diff --git a/.circleci/config.yml b/.circleci/config.yml index 690e7f45a2e..648eed2dd4b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,12 +2,6 @@ # See https://app.circleci.com/pipelines/github/vimeo/psalm version: 2.1 executors: - php-74: - docker: - - image: thecodingmachine/php:7.4-v4-cli - php-80: - docker: - - image: thecodingmachine/php:8.0-v4-cli php-81: docker: - image: thecodingmachine/php:8.1-v4-cli @@ -16,7 +10,7 @@ executors: - image: thecodingmachine/php:8.2-v4-cli jobs: "Code Style Analysis": - executor: php-74 + executor: php-81 steps: - checkout @@ -44,7 +38,7 @@ jobs: command: vendor/bin/phpcs -d memory_limit=512M phar-build: - executor: php-74 + executor: php-81 steps: - attach_workspace: at: /home/docker/project/ diff --git a/.github/workflows/build-phar.yml b/.github/workflows/build-phar.yml index 51abbb71970..a46e7f240d7 100644 --- a/.github/workflows/build-phar.yml +++ b/.github/workflows/build-phar.yml @@ -38,7 +38,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.1' tools: composer:v2 coverage: none env: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 811051df702..cfc2ddc5045 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -12,7 +12,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.1' tools: composer:v2 coverage: none env: @@ -32,6 +32,12 @@ jobs: env: COMPOSER_ROOT_VERSION: dev-master + - name: Check all classes are autoloadable + run: | + composer dump-autoload -o --strict-psr + env: + COMPOSER_ROOT_VERSION: dev-master + - name: Cache composer cache uses: actions/cache@v4 with: @@ -57,7 +63,7 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: '7.4' + php-version: '8.1' tools: composer:v2 coverage: none env: @@ -125,7 +131,6 @@ jobs: fail-fast: false matrix: php-version: - - "8.0" - "8.1" - "8.2" - "8.3" diff --git a/.github/workflows/macos-scan.yml b/.github/workflows/macos-scan.yml new file mode 100644 index 00000000000..c44ac995e76 --- /dev/null +++ b/.github/workflows/macos-scan.yml @@ -0,0 +1,29 @@ +name: Run Psalm (mac OS) + +on: [push, pull_request] + +permissions: + contents: read + +jobs: + build: + runs-on: macos-15 + + steps: + - uses: actions/checkout@v4 + - uses: shivammathur/setup-php@v2 + with: + php-version: '8.4' + ini-values: zend.assertions=1 + tools: composer:v2 + coverage: none + env: + fail-fast: true + + - name: Install dependencies + run: composer install --prefer-dist --no-progress --no-suggest + env: + COMPOSER_ROOT_VERSION: dev-master + + - name: Run Psalm + run: ./psalm --output-format=github --force-jit diff --git a/.github/workflows/shepherd.yml b/.github/workflows/shepherd.yml index 3440783d936..41d6acd4d4a 100644 --- a/.github/workflows/shepherd.yml +++ b/.github/workflows/shepherd.yml @@ -13,7 +13,7 @@ jobs: - uses: actions/checkout@v4 - uses: shivammathur/setup-php@v2 with: - php-version: '8.2' + php-version: '8.4' ini-values: zend.assertions=1 tools: composer:v2 coverage: none @@ -26,4 +26,4 @@ jobs: COMPOSER_ROOT_VERSION: dev-master - name: Run Psalm - run: ./psalm --threads=2 --output-format=github --shepherd + run: ./psalm --output-format=github --shepherd --force-jit diff --git a/.github/workflows/windows-ci.yml b/.github/workflows/windows-ci.yml index d17fd73547c..860208ab427 100644 --- a/.github/workflows/windows-ci.yml +++ b/.github/workflows/windows-ci.yml @@ -54,14 +54,21 @@ jobs: - name: Set up PHP uses: shivammathur/setup-php@v2 with: - php-version: '8.0' - ini-values: zend.assertions=1, assert.exception=1, opcache.enable_cli=1, opcache.jit=function, opcache.jit_buffer_size=512M + php-version: '8.1' + #ini-values: zend.assertions=1, assert.exception=1, opcache.enable_cli=1, opcache.jit=function, opcache.jit_buffer_size=512M + ini-values: zend.assertions=1, assert.exception=1 tools: composer:v2 coverage: none - extensions: none, curl, dom, filter, intl, json, libxml, mbstring, openssl, opcache, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter + #extensions: none, curl, dom, filter, intl, json, libxml, mbstring, openssl, opcache, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter + extensions: none, curl, dom, filter, intl, json, libxml, mbstring, openssl, pcre, phar, reflection, simplexml, spl, tokenizer, xml, xmlwriter env: fail-fast: true + - name: PHP Version + run: | + php -v + php -r 'var_dump(PHP_VERSION_ID);' + - uses: actions/checkout@v4 - name: Get Composer Cache Directories diff --git a/UPGRADING.md b/UPGRADING.md index 01f1a67ddda..767e293871e 100644 --- a/UPGRADING.md +++ b/UPGRADING.md @@ -1,6 +1,10 @@ # Upgrading from Psalm 5 to Psalm 6 ## Changed +- The minimum PHP version was raised to PHP 8.1.17. + +- [BC] The configuration settings `ignoreInternalFunctionFalseReturn` and `ignoreInternalFunctionNullReturn` are now defaulted to `false` + - [BC] Switched the internal representation of `list` and `non-empty-list` from the TList and TNonEmptyList classes to an unsealed list shape: the TList, TNonEmptyList and TCallableList classes were removed. Nothing will change for users: the `list` and `non-empty-list` syntax will remain supported and its semantics unchanged. Psalm 5 already deprecates the `TList`, `TNonEmptyList` and `TCallableList` classes: use `\Psalm\Type::getListAtomic`, `\Psalm\Type::getNonEmptyListAtomic` and `\Psalm\Type::getCallableListAtomic` to instantiate list atomics, or directly instantiate TKeyedArray objects with `is_list=true` where appropriate. @@ -8,6 +12,48 @@ - [BC] The only optional boolean parameter of `TKeyedArray::getGenericArrayType` was removed, and was replaced with a string parameter with a different meaning. - [BC] The `TDependentListKey` type was removed and replaced with an optional property of the `TIntRange` type. +- +- [BC] `TCallableArray` and `TCallableList` removed and replaced with `TCallableKeyedArray`. + +- [BC] Class `Psalm\Issue\MixedInferredReturnType` was removed + +- [BC] Value of constant `Psalm\Type\TaintKindGroup::ALL_INPUT` changed to reflect new `TaintKind::INPUT_EXTRACT`, `TaintKind::INPUT_SLEEP` and `TaintKind::INPUT_XPATH` have been added. Accordingly, default values for `$taint` parameters of `Psalm\Codebase::addTaintSource()` and `Psalm\Codebase::addTaintSink()` have been changed as well. + +- [BC] Property `Config::$shepherd_host` was replaced with `Config::$shepherd_endpoint` + +- [BC] Methods `Codebase::getSymbolLocation()` and `Codebase::getSymbolInformation()` were replaced with `Codebase::getSymbolLocationByReference()` + +- [BC] Method `Psalm\Type\Atomic\TKeyedArray::getList()` was removed + +- [BC] Method `Psalm\Storage\FunctionLikeStorage::getSignature()` was replaced with `FunctionLikeStorage::getCompletionSignature()` + +- [BC] Property `Psalm\Storage\FunctionLikeStorage::$unused_docblock_params` was replaced with `FunctionLikeStorage::$unused_docblock_parameters` + +- [BC] Method `Plugin\Shepherd::getCurlErrorMessage()` was removed + +- [BC] Property `Config::$find_unused_code` changed default value from false to true + +- [BC] Property `Config::$find_unused_baseline_entry` changed default value from false to true + +- [BC] The return type of `Psalm\Internal\LanguageServer\ProtocolWriter#write() changed from `Amp\Promise` to `void` due to the switch to Amp v3 + +- [BC] All parameters, properties and return typehints are now strictly typed. + +- [BC] `strict_types` is now applied to all files of the Psalm codebase. + +- [BC] Properties `Psalm\Type\Atomic\TLiteralFloat::$value` and `Psalm\Type\Atomic\TLiteralInt::$value` became typed (`float` and `int` respectively) + +- [BC] Property `Psalm\Storage\EnumCaseStorage::$value` changed from `int|string|null` to `TLiteralInt|TLiteralString|null` + +- [BC] `Psalm\CodeLocation\Raw`, `Psalm\CodeLocation\ParseErrorLocation`, `Psalm\CodeLocation\DocblockTypeLocation`, `Psalm\Report\CountReport`, `Psalm\Type\Atomic\TNonEmptyArray` are now all final. + +- [BC] `Psalm\Config` is now final. + +- [BC] The return type of `Psalm\Plugin\ArgTypeInferer::infer` changed from `Union|false` to `Union|null` + +- [BC] The `extra_types` property and `setIntersectionTypes` method of `Psalm\Type\Atomic\TTypeAlias` were removed. + +- [BC] Methods `convertSeverity` and `calculateFingerprint` of `Psalm\Report\CodeClimateReport` were removed. # Upgrading from Psalm 4 to Psalm 5 ## Changed diff --git a/bin/generate_issues_list_doc.php b/bin/generate_issues_list_doc.php index 8a3e179ca2d..0b324af3504 100755 --- a/bin/generate_issues_list_doc.php +++ b/bin/generate_issues_list_doc.php @@ -1,6 +1,8 @@ #!/usr/bin/env php ' . PHP_EOL); exit(1); @@ -35,7 +37,7 @@ ); array_multisort($order, $files); -$chunks = array_chunk($files, ceil(count($files) / $number_of_chunks)); +$chunks = array_chunk($files, (int) ceil(count($files) / $number_of_chunks)); $phpunit_config = new DOMDocument('1.0', 'UTF-8'); $phpunit_config->preserveWhiteSpace = false; diff --git a/bin/improve_class_alias.php b/bin/improve_class_alias.php index 6b6f34ff299..014fc4144e9 100644 --- a/bin/improve_class_alias.php +++ b/bin/improve_class_alias.php @@ -1,5 +1,7 @@ ]+>#', '', $contents); + $contents = (string) preg_replace('#&[a-zA-Z\d.\-_]+;#', '', $contents); + $contents = (string) preg_replace('#%[a-zA-Z\d.\-_]+;#', '', $contents); + $contents = (string) preg_replace('#]+>#', '', $contents); try { $simple = new SimpleXMLElement($contents); } catch (Throwable $exception) { diff --git a/composer.json b/composer.json index 602928094c4..8b5c964183b 100644 --- a/composer.json +++ b/composer.json @@ -15,7 +15,7 @@ } ], "require": { - "php": "^7.4 || ~8.0.0 || ~8.1.0 || ~8.2.0 || ~8.3.0 || ~8.4.0", + "php": "~8.1.17 || ~8.2.4 || ~8.3.0 || ~8.4.0", "ext-SimpleXML": "*", "ext-ctype": "*", "ext-dom": "*", @@ -24,8 +24,8 @@ "ext-mbstring": "*", "ext-tokenizer": "*", "composer-runtime-api": "^2", - "amphp/amp": "^2.4.2", - "amphp/byte-stream": "^1.5", + "amphp/amp": "^3", + "amphp/byte-stream": "^2", "composer/semver": "^1.4 || ^2.0 || ^3.0", "composer/xdebug-handler": "^2.0 || ^3.0", "dnoegel/php-xdg-base-dir": "^0.1.1", @@ -33,23 +33,21 @@ "felixfbecker/language-server-protocol": "^1.5.3", "fidry/cpu-core-counter": "^0.4.1 || ^0.5.1 || ^1.0.0", "netresearch/jsonmapper": "^1.0 || ^2.0 || ^3.0 || ^4.0", - "nikic/php-parser": "^4.17", + "nikic/php-parser": "^5.0.0", "sebastian/diff": "^4.0 || ^5.0 || ^6.0", "spatie/array-to-xml": "^2.17.0 || ^3.0", "symfony/console": "^4.1.6 || ^5.0 || ^6.0 || ^7.0", "symfony/filesystem": "^5.4 || ^6.0 || ^7.0" }, - "conflict": { - "nikic/php-parser": "4.17.0" - }, "provide": { "psalm/psalm": "self.version" }, "require-dev": { "ext-curl": "*", - "amphp/phpunit-util": "^2.0.1", + "amphp/phpunit-util": "^3", "bamarni/composer-bin-plugin": "^1.4", "brianium/paratest": "^6.9", + "dg/bypass-finals": "^1.5", "mockery/mockery": "^1.5", "nunomaduro/mock-final-classes": "^1.1", "php-parallel-lint/php-parallel-lint": "^1.2", @@ -77,7 +75,8 @@ }, "extra": { "branch-alias": { - "dev-master": "5.x-dev", + "dev-master": "6.x-dev", + "dev-5.x": "5.x-dev", "dev-4.x": "4.x-dev", "dev-3.x": "3.x-dev", "dev-2.x": "2.x-dev", @@ -109,7 +108,7 @@ "lint": "@php parallel-lint ./src ./tests", "phpunit": [ "Composer\\Config::disableProcessTimeout", - "@php paratest --runner=WrapperRunner" + "paratest -f --runner=WrapperRunner" ], "phpunit-std": [ "Composer\\Config::disableProcessTimeout", @@ -117,7 +116,7 @@ ], "verify-callmap": "@php phpunit tests/Internal/Codebase/InternalCallMapHandlerTest.php", "psalm": "@php ./psalm", - "psalm-set-baseline": "@php ./psalm --set-baseline=psalm-baseline.xml", + "psalm-set-baseline": "@php ./psalm --set-baseline", "tests": [ "@lint", "@cs", @@ -135,8 +134,8 @@ "tests": "Runs all available tests." }, "support": { - "docs": "https://psalm.dev/docs", - "issues": "https://github.com/vimeo/psalm/issues", - "source": "https://github.com/vimeo/psalm" + "docs": "https://psalm.dev/docs", + "issues": "https://github.com/vimeo/psalm/issues", + "source": "https://github.com/vimeo/psalm" } } diff --git a/config.xsd b/config.xsd index 9d785d39b04..b58a3bbb9d7 100644 --- a/config.xsd +++ b/config.xsd @@ -48,10 +48,11 @@ + - - + + @@ -231,6 +232,7 @@ + @@ -336,7 +338,6 @@ - @@ -354,6 +355,7 @@ + @@ -437,6 +439,7 @@ + @@ -444,12 +447,14 @@ + + @@ -496,6 +501,7 @@ + diff --git a/docs/annotating_code/supported_annotations.md b/docs/annotating_code/supported_annotations.md index 1879d602ab5..8ea1e83eace 100644 --- a/docs/annotating_code/supported_annotations.md +++ b/docs/annotating_code/supported_annotations.md @@ -6,23 +6,23 @@ Psalm supports a wide range of docblock annotations. Psalm uses the following PHPDoc tags to understand your code: -- [`@var`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/var.html) +- [`@var`](https://docs.phpdoc.org/guide/references/phpdoc/tags/var.html) Used for specifying the types of properties and variables -- [`@return`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/return.html) +- [`@return`](https://docs.phpdoc.org/guide/references/phpdoc/tags/return.html) Used for specifying the return types of functions, methods and closures -- [`@param`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/param.html) +- [`@param`](https://docs.phpdoc.org/guide/references/phpdoc/tags/param.html) Used for specifying types of parameters passed to functions, methods and closures -- [`@property`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/property.html) +- [`@property`](https://docs.phpdoc.org/guide/references/phpdoc/tags/property.html) Used to specify what properties can be accessed on an object that uses `__get` and `__set` -- [`@property-read`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/property.html) +- [`@property-read`](https://docs.phpdoc.org/guide/references/phpdoc/tags/property.html) Used to specify what properties can be read on object that uses `__get` -- [`@property-write`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/property.html) +- [`@property-write`](https://docs.phpdoc.org/guide/references/phpdoc/tags/property.html) Used to specify what properties can be written on object that uses `__set` -- [`@method`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/method.html) +- [`@method`](https://docs.phpdoc.org/guide/references/phpdoc/tags/method.html) Used to specify which magic methods are available on object that uses `__call`. -- [`@deprecated`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/deprecated.html) +- [`@deprecated`](https://docs.phpdoc.org/guide/references/phpdoc/tags/deprecated.html) Used to mark functions, methods, classes and interfaces as being deprecated -- [`@internal`](https://docs.phpdoc.org/latest/guide/references/phpdoc/tags/internal.html) +- [`@internal`](https://docs.phpdoc.org/guide/references/phpdoc/tags/internal.html) Used to mark classes, functions and properties that are internal to an application or library. - [`@mixin`](#mixins) Used to tell Psalm that the current class proxies the methods and properties of the referenced class. @@ -202,7 +202,7 @@ takesFoo(getFoo()); This provides the same, but for `false`. Psalm uses this internally for functions like `preg_replace`, which can return false if the given input has encoding errors, but where 99.9% of the time the function operates as expected. -### `@psalm-seal-properties`, `@psalm-no-seal-properties` +### `@psalm-seal-properties`, `@psalm-no-seal-properties`, `@seal-properties`, `@no-seal-properties` If you have a magic property getter/setter, you can use `@psalm-seal-properties` to instruct Psalm to disallow getting and setting any properties not contained in a list of `@property` (or `@property-read`/`@property-write`) annotations. This is automatically enabled with the configuration option `sealAllProperties` and can be disabled for a class with `@psalm-no-seal-properties` @@ -211,7 +211,7 @@ This is automatically enabled with the configuration option `sealAllProperties` bar = 5; // this call fails ``` -### `@psalm-seal-methods`, `@psalm-no-seal-methods` +### `@psalm-seal-methods`, `@psalm-no-seal-methods`, `@seal-methods`, `@no-seal-methods` If you have a magic method caller, you can use `@psalm-seal-methods` to instruct Psalm to disallow calling any methods not contained in a list of `@method` annotations. This is automatically enabled with the configuration option `sealAllMethods` and can be disabled for a class with `@psalm-no-seal-methods` @@ -236,7 +236,7 @@ This is automatically enabled with the configuration option `sealAllMethods` and `, 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 diff --git a/docs/annotating_code/typing_in_psalm.md b/docs/annotating_code/typing_in_psalm.md index 4bb827f6696..16ea466b6b7 100644 --- a/docs/annotating_code/typing_in_psalm.md +++ b/docs/annotating_code/typing_in_psalm.md @@ -10,7 +10,7 @@ Psalm allows you to express a lot of complicated type information in docblocks. All docblock types are either [atomic types](type_syntax/atomic_types.md), [union types](type_syntax/union_types.md) or [intersection types](type_syntax/intersection_types.md). -Additionally, Psalm supports PHPDoc’s [type syntax](https://docs.phpdoc.org/latest/guide/guides/types.html), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types). +Additionally, Psalm supports PHPDoc’s [type syntax](https://docs.phpdoc.org/guide/guides/types.html#supported-types), and also the [proposed PHPDoc PSR type syntax](https://github.com/php-fig/fig-standards/blob/master/proposed/phpdoc.md#appendix-a-types). ## Property declaration types vs Assignment typehints diff --git a/docs/contributing/adding_issues.md b/docs/contributing/adding_issues.md index b609d872837..4cc2fabb4b9 100644 --- a/docs/contributing/adding_issues.md +++ b/docs/contributing/adding_issues.md @@ -17,8 +17,8 @@ namespace Psalm\Issue; final class MyNewIssue extends CodeIssue { - public const SHORTCODE = 123; public const ERROR_LEVEL = 2; + public const SHORTCODE = 123; } ``` @@ -26,7 +26,7 @@ For `SHORTCODE` value use `$max_shortcode + 1`. To choose appropriate error leve There a number of abstract classes you can extend: -* `CodeIssue` - non specific, default issue. It's a base class for all issues. +* `CodeIssue` - non-specific, default issue. It's a base class for all issues. * `ClassIssue` - issue related to a specific class (also interface, trait, enum). These issues can be suppressed for specific classes in `psalm.xml` by using `referencedClass` attribute * `PropertyIssue` - issue related to a specific property. Can be targeted by using `referencedProperty` in `psalm.xml` * `FunctionIssue` - issue related to a specific function. Can be suppressed with `referencedFunction` attribute. diff --git a/docs/running_psalm/configuration.md b/docs/running_psalm/configuration.md index d00baae88d3..356dff1cf68 100644 --- a/docs/running_psalm/configuration.md +++ b/docs/running_psalm/configuration.md @@ -213,7 +213,7 @@ When `true`, Psalm will check that the developer has caught every exception in g ignoreInternalFunctionFalseReturn="[bool]" > ``` -When `true`, Psalm ignores possibly-false issues stemming from return values of internal functions (like `preg_split`) that may return false, but do so rarely. Defaults to `true`. +When `true`, Psalm ignores possibly-false issues stemming from return values of internal functions (like `preg_split`) that may return false, but do so rarely. Defaults to `false`. #### ignoreInternalFunctionNullReturn @@ -222,7 +222,7 @@ When `true`, Psalm ignores possibly-false issues stemming from return values of ignoreInternalFunctionNullReturn="[bool]" > ``` -When `true`, Psalm ignores possibly-null issues stemming from return values of internal array functions (like `current`) that may return null, but do so rarely. Defaults to `true`. +When `true`, Psalm ignores possibly-null issues stemming from return values of internal array functions (like `current`) that may return null, but do so rarely. Defaults to `false`. #### inferPropertyTypesFromConstructor @@ -444,10 +444,10 @@ Allows you to hard-code the number of threads Psalm will use (similar to `--thre maxStringLength="1000" > ``` -This setting controls the maximum length of literal strings that will be transformed into a literal string type during Psalm analysis. -Strings longer than this value (by default 1000 bytes) will be transformed in a generic `non-empty-string` type, instead. +This setting controls the maximum length of literal strings that will be transformed into a literal string type during Psalm analysis. +Strings longer than this value (by default 1000 bytes) will be transformed in a generic `non-empty-string` type, instead. -Please note that changing this setting might introduce unwanted side effects and those side effects won't be considered as bugs. +Please note that changing this setting might introduce unwanted side effects and those side effects won't be considered as bugs. #### maxShapedArraySize ```xml @@ -455,10 +455,10 @@ Please note that changing this setting might introduce unwanted side effects and maxShapedArraySize="100" > ``` -This setting controls the maximum size of shaped arrays that will be transformed into a shaped `array{key1: "value", key2: T}` type during Psalm analysis. -Arrays bigger than this value (100 by default) will be transformed in a generic `non-empty-array` type, instead. +This setting controls the maximum size of shaped arrays that will be transformed into a shaped `array{key1: "value", key2: T}` type during Psalm analysis. +Arrays bigger than this value (100 by default) will be transformed in a generic `non-empty-array` type, instead. -Please note that changing this setting might introduce unwanted side effects and those side effects won't be considered as bugs. +Please note that changing this setting might introduce unwanted side effects and those side effects won't be considered as bugs. #### restrictReturnTypes @@ -474,20 +474,20 @@ the inferred return type. This code: ```php function getOne(): int // declared type: int -{ +{ return 1; // inferred type: 1 (int literal) } ``` Will give this error: `LessSpecificReturnType - The inferred return type '1' for -a is more specific than the declared return type 'int'` +getOne is more specific than the declared return type 'int'` To fix the error, you should specify the more specific type in the doc-block: ```php /** * @return 1 */ -function getOne(): int -{ +function getOne(): int +{ return 1; } ``` @@ -521,6 +521,11 @@ class PremiumCar extends StandardCar { Emits [UnusedBaselineEntry](issues/UnusedBaselineEntry.md) when a baseline entry is not being used to suppress an issue. +#### findUnusedIssueHandlerSuppression + +Emits [UnusedIssueHandlerSuppression](issues/UnusedIssueHandlerSuppression.md) when a suppressed issue handler +is not being used to suppress an issue. + ## Project settings #### <projectFiles> diff --git a/docs/running_psalm/dealing_with_code_issues.md b/docs/running_psalm/dealing_with_code_issues.md index 552dede75bc..f26c762d600 100644 --- a/docs/running_psalm/dealing_with_code_issues.md +++ b/docs/running_psalm/dealing_with_code_issues.md @@ -27,7 +27,7 @@ Some issue types allow the use of `referencedMethod`, `referencedClass` or `refe ```xml - + @@ -95,11 +95,17 @@ If you wish to suppress all issues, you can use `@psalm-suppress all` instead of If you have a bunch of errors and you don't want to fix them all at once, Psalm can grandfather-in errors in existing code, while ensuring that new code doesn't have those same sorts of errors. +``` +vendor/bin/psalm --set-baseline +``` + +will generate a file `psalm-baseline.xml` containing the current errors. Alternatively, you can specify the name of your baseline file. + ``` vendor/bin/psalm --set-baseline=your-baseline.xml ``` -will generate a file containing the current errors. You should commit that generated file so that Psalm can use it when running in other places (e.g. CI). It won't complain about those errors either. +You should commit that generated file so that Psalm can use it when running in other places (e.g. CI). It won't complain about those errors either. You have two options to use the generated baseline when running psalm: diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md index 155e42671e2..7e5fc58dfb4 100644 --- a/docs/running_psalm/error_levels.md +++ b/docs/running_psalm/error_levels.md @@ -29,6 +29,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [DuplicateFunction](issues/DuplicateFunction.md) - [DuplicateMethod](issues/DuplicateMethod.md) - [DuplicateParam](issues/DuplicateParam.md) + - [DuplicateProperty](issues/DuplicateProperty.md) - [EmptyArrayAccess](issues/EmptyArrayAccess.md) - [ExtensionRequirementViolation](issues/ExtensionRequirementViolation.md) - [ImplementationRequirementViolation](issues/ImplementationRequirementViolation.md) @@ -101,6 +102,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [InvalidOverride](issues/InvalidOverride.md) - [InvalidTypeImport](issues/InvalidTypeImport.md) - [MethodSignatureMismatch](issues/MethodSignatureMismatch.md) +- [NonVariableReferenceReturn](issues/NonVariableReferenceReturn.md) - [OverriddenMethodAccess](issues/OverriddenMethodAccess.md) - [ParamNameMismatch](issues/ParamNameMismatch.md) - [ReservedWord](issues/ReservedWord.md) @@ -263,7 +265,6 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [MixedAssignment](issues/MixedAssignment.md) - [MixedClone](issues/MixedClone.md) - [MixedFunctionCall](issues/MixedFunctionCall.md) - - [MixedInferredReturnType](issues/MixedInferredReturnType.md) - [MixedMethodCall](issues/MixedMethodCall.md) - [MixedOperand](issues/MixedOperand.md) - [MixedPropertyAssignment](issues/MixedPropertyAssignment.md) @@ -289,6 +290,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [TaintedCookie](issues/TaintedCookie.md) - [TaintedCustom](issues/TaintedCustom.md) - [TaintedEval](issues/TaintedEval.md) + - [TaintedExtract](issues/TaintedExtract.md) - [TaintedFile](issues/TaintedFile.md) - [TaintedHeader](issues/TaintedHeader.md) - [TaintedHtml](issues/TaintedHtml.md) @@ -296,11 +298,13 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [TaintedInput](issues/TaintedInput.md) - [TaintedLdap](issues/TaintedLdap.md) - [TaintedShell](issues/TaintedShell.md) + - [TaintedSleep](issues/TaintedSleep.md) - [TaintedSql](issues/TaintedSql.md) - [TaintedSSRF](issues/TaintedSSRF.md) - [TaintedSystemSecret](issues/TaintedSystemSecret.md) - [TaintedUnserialize](issues/TaintedUnserialize.md) - [TaintedUserSecret](issues/TaintedUserSecret.md) + - [TaintedXpath](issues/TaintedXpath.md) - [UncaughtThrowInGlobalScope](issues/UncaughtThrowInGlobalScope.md) - [UnevaluatedCode](issues/UnevaluatedCode.md) - [UnnecessaryVarAnnotation](issues/UnnecessaryVarAnnotation.md) diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md index 384e3703b3a..5a006d1a15f 100644 --- a/docs/running_psalm/issues.md +++ b/docs/running_psalm/issues.md @@ -31,6 +31,7 @@ - [DuplicateFunction](issues/DuplicateFunction.md) - [DuplicateMethod](issues/DuplicateMethod.md) - [DuplicateParam](issues/DuplicateParam.md) + - [DuplicateProperty](issues/DuplicateProperty.md) - [EmptyArrayAccess](issues/EmptyArrayAccess.md) - [ExtensionRequirementViolation](issues/ExtensionRequirementViolation.md) - [FalsableReturnStatement](issues/FalsableReturnStatement.md) @@ -136,7 +137,6 @@ - [MixedAssignment](issues/MixedAssignment.md) - [MixedClone](issues/MixedClone.md) - [MixedFunctionCall](issues/MixedFunctionCall.md) - - [MixedInferredReturnType](issues/MixedInferredReturnType.md) - [MixedMethodCall](issues/MixedMethodCall.md) - [MixedOperand](issues/MixedOperand.md) - [MixedPropertyAssignment](issues/MixedPropertyAssignment.md) @@ -154,6 +154,7 @@ - [NonInvariantDocblockPropertyType](issues/NonInvariantDocblockPropertyType.md) - [NonInvariantPropertyType](issues/NonInvariantPropertyType.md) - [NonStaticSelfCall](issues/NonStaticSelfCall.md) + - [NonVariableReferenceReturn](issues/NonVariableReferenceReturn.md) - [NoValue](issues/NoValue.md) - [NullableReturnStatement](issues/NullableReturnStatement.md) - [NullArgument](issues/NullArgument.md) @@ -238,6 +239,7 @@ - [TaintedCookie](issues/TaintedCookie.md) - [TaintedCustom](issues/TaintedCustom.md) - [TaintedEval](issues/TaintedEval.md) + - [TaintedExtract](issues/TaintedExtract.md) - [TaintedFile](issues/TaintedFile.md) - [TaintedHeader](issues/TaintedHeader.md) - [TaintedHtml](issues/TaintedHtml.md) @@ -245,12 +247,14 @@ - [TaintedInput](issues/TaintedInput.md) - [TaintedLdap](issues/TaintedLdap.md) - [TaintedShell](issues/TaintedShell.md) + - [TaintedSleep](issues/TaintedSleep.md) - [TaintedSql](issues/TaintedSql.md) - [TaintedSSRF](issues/TaintedSSRF.md) - [TaintedSystemSecret](issues/TaintedSystemSecret.md) - [TaintedTextWithQuotes](issues/TaintedTextWithQuotes.md) - [TaintedUnserialize](issues/TaintedUnserialize.md) - [TaintedUserSecret](issues/TaintedUserSecret.md) + - [TaintedXpath](issues/TaintedXpath.md) - [TooFewArguments](issues/TooFewArguments.md) - [TooManyArguments](issues/TooManyArguments.md) - [TooManyTemplateParams](issues/TooManyTemplateParams.md) @@ -299,6 +303,7 @@ - [UnusedDocblockParam](issues/UnusedDocblockParam.md) - [UnusedForeachValue](issues/UnusedForeachValue.md) - [UnusedFunctionCall](issues/UnusedFunctionCall.md) + - [UnusedIssueHandlerSuppression](issues/UnusedIssueHandlerSuppression.md) - [UnusedMethod](issues/UnusedMethod.md) - [UnusedMethodCall](issues/UnusedMethodCall.md) - [UnusedParam](issues/UnusedParam.md) diff --git a/docs/running_psalm/issues/DuplicateProperty.md b/docs/running_psalm/issues/DuplicateProperty.md new file mode 100644 index 00000000000..1a7cf0e6e59 --- /dev/null +++ b/docs/running_psalm/issues/DuplicateProperty.md @@ -0,0 +1,19 @@ +# DuplicateProperty + +Emitted when a class property is defined twice + +```php +xpath($expression); +} +``` diff --git a/docs/running_psalm/issues/UnusedIssueHandlerSuppression.md b/docs/running_psalm/issues/UnusedIssueHandlerSuppression.md new file mode 100644 index 00000000000..dc796e35265 --- /dev/null +++ b/docs/running_psalm/issues/UnusedIssueHandlerSuppression.md @@ -0,0 +1,17 @@ +# UnusedIssueHandlerSuppression + +Emitted when an issue type suppression in the configuration file is not being used to suppress an issue. + +Enabled by [findUnusedIssueHandlerSuppression](../configuration.md#findunusedissuehandlersuppression) + +```php + + + + +``` diff --git a/docs/running_psalm/plugins/authoring_plugins.md b/docs/running_psalm/plugins/authoring_plugins.md index 2e3043de74a..eb14e8dd91b 100644 --- a/docs/running_psalm/plugins/authoring_plugins.md +++ b/docs/running_psalm/plugins/authoring_plugins.md @@ -80,8 +80,8 @@ class SomePlugin implements \Psalm\Plugin\EventHandler\AfterStatementAnalysisInt - `AfterFunctionCallAnalysisInterface` - called after Psalm evaluates a function call to any function defined within the project itself. Can alter the return type or perform modifications of the call. - `AfterFunctionLikeAnalysisInterface` - called after Psalm has completed its analysis of a given function-like. - `AfterMethodCallAnalysisInterface` - called after Psalm analyzes a method call. -- `BeforeStatementAnalysisInterface` - called before Psalm evaluates an statement. -- `AfterStatementAnalysisInterface` - called after Psalm evaluates an statement. +- `BeforeStatementAnalysisInterface` - called before Psalm evaluates a statement. +- `AfterStatementAnalysisInterface` - called after Psalm evaluates a statement. - `BeforeAddIssueInterface` - called before Psalm adds an item to it's internal `IssueBuffer`, allows handling code issues individually - `BeforeFileAnalysisInterface` - called before Psalm analyzes a file. - `FunctionExistenceProviderInterface` - can be used to override Psalm's builtin function existence checks for one or more functions. diff --git a/docs/running_psalm/plugins/plugins_type_system.md b/docs/running_psalm/plugins/plugins_type_system.md index 5cf70ad94e7..99e6fa6b807 100644 --- a/docs/running_psalm/plugins/plugins_type_system.md +++ b/docs/running_psalm/plugins/plugins_type_system.md @@ -183,8 +183,6 @@ $a = []; foreach (range(1,1) as $_) $a[(string)rand(0,1)] = rand(0,1); // array ``` -`TCallableArray` - denotes an array that is _also_ `callable`. - `TCallableKeyedArray` - denotes an object-like array that is _also_ `callable`. `TClassStringMap` - Represents an array where the type of each value is a function of its string key value diff --git a/examples/TemplateChecker.php b/examples/TemplateChecker.php index ddaaab2baab..4e911dd268d 100644 --- a/examples/TemplateChecker.php +++ b/examples/TemplateChecker.php @@ -32,7 +32,7 @@ final class TemplateAnalyzer extends Psalm\Internal\Analyzer\FileAnalyzer { - const VIEW_CLASS = 'Your\\View\\Class'; + final public const VIEW_CLASS = 'Your\\View\\Class'; public function analyze(?Context $file_context = null, ?Context $global_context = null): void { @@ -148,7 +148,7 @@ private function checkMethod(MethodIdentifier $method_id, PhpParser\Node $stmt, /** * @param array $stmts */ - protected function checkWithViewClass(Context $context, array $stmts): void + private function checkWithViewClass(Context $context, array $stmts): void { $pseudo_method_stmts = []; @@ -160,7 +160,7 @@ protected function checkWithViewClass(Context $context, array $stmts): void } } - $pseudo_method_name = preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->file_name); + $pseudo_method_name = (string) preg_replace('/[^a-zA-Z0-9_]+/', '_', $this->file_name); $class_method = new VirtualClassMethod($pseudo_method_name, ['stmts' => []]); diff --git a/examples/TemplateScanner.php b/examples/TemplateScanner.php index 70c37470ca0..254b06dc338 100644 --- a/examples/TemplateScanner.php +++ b/examples/TemplateScanner.php @@ -16,7 +16,7 @@ final class TemplateScanner extends Psalm\Internal\Scanner\FileScanner { - const VIEW_CLASS = 'Your\\View\\Class'; + final public const VIEW_CLASS = 'Your\\View\\Class'; public function scan( Codebase $codebase, diff --git a/examples/plugins/ClassUnqualifier.php b/examples/plugins/ClassUnqualifier.php index 2c84f051ef1..44104376292 100644 --- a/examples/plugins/ClassUnqualifier.php +++ b/examples/plugins/ClassUnqualifier.php @@ -29,7 +29,7 @@ public static function afterClassLikeExistenceCheck( return; } - if (strpos($candidate_type, '\\' . $fq_class_name) !== false) { + if (str_contains($candidate_type, '\\' . $fq_class_name)) { $type_tokens = TypeTokenizer::tokenize($candidate_type, false); foreach ($type_tokens as &$type_token) { diff --git a/examples/plugins/FunctionCasingChecker.php b/examples/plugins/FunctionCasingChecker.php index f4147ab3f3f..56ec8983203 100644 --- a/examples/plugins/FunctionCasingChecker.php +++ b/examples/plugins/FunctionCasingChecker.php @@ -55,7 +55,7 @@ public static function afterMethodCallAnalysis(AfterMethodCallAnalysisEvent $eve $statements_source->getSuppressedIssues(), ); } - } catch (Exception $e) { + } catch (Exception) { // can throw if storage is missing } } @@ -93,7 +93,7 @@ public static function afterFunctionCallAnalysis(AfterFunctionCallAnalysisEvent $statements_source->getSuppressedIssues(), ); } - } catch (Exception $e) { + } catch (Exception) { // can throw if storage is missing } } diff --git a/examples/plugins/InternalChecker.php b/examples/plugins/InternalChecker.php index 802edccf87d..0b2ea575187 100644 --- a/examples/plugins/InternalChecker.php +++ b/examples/plugins/InternalChecker.php @@ -19,7 +19,7 @@ public static function afterStatementAnalysis(AfterClassLikeAnalysisEvent $event { $storage = $event->getClasslikeStorage(); if (!$storage->internal - && strpos($storage->name, 'Psalm\\Internal') === 0 + && str_starts_with($storage->name, 'Psalm\\Internal') && $storage->location ) { IssueBuffer::maybeAdd( diff --git a/examples/plugins/SafeArrayKeyChecker.php b/examples/plugins/SafeArrayKeyChecker.php index 0360ed79155..fafcc2727da 100644 --- a/examples/plugins/SafeArrayKeyChecker.php +++ b/examples/plugins/SafeArrayKeyChecker.php @@ -2,7 +2,7 @@ namespace Psalm\Example\Plugin; -use PhpParser\Node\Expr\ArrayItem; +use PhpParser\Node\ArrayItem; use Psalm\Internal\Analyzer\StatementsAnalyzer; use Psalm\Plugin\EventHandler\Event\AddRemoveTaintsEvent; use Psalm\Plugin\EventHandler\RemoveTaintsInterface; diff --git a/examples/plugins/StringChecker.php b/examples/plugins/StringChecker.php index 8366596ce33..1ce7a814086 100644 --- a/examples/plugins/StringChecker.php +++ b/examples/plugins/StringChecker.php @@ -31,10 +31,11 @@ public static function afterExpressionAnalysis(AfterExpressionAnalysisEvent $eve if ($expr instanceof PhpParser\Node\Scalar\String_) { $class_or_class_method = '/^\\\?Psalm(\\\[A-Z][A-Za-z0-9]+)+(::[A-Za-z0-9]+)?$/'; - if (strpos($statements_source->getFileName(), 'base/DefinitionManager.php') === false - && strpos($expr->value, 'TestController') === false + if (!str_contains($statements_source->getFileName(), 'base/DefinitionManager.php') + && !str_contains($expr->value, 'TestController') && preg_match($class_or_class_method, $expr->value) ) { + /** @psalm-suppress PossiblyInvalidArrayAccess */ $absolute_class = preg_split('/[:]/', $expr->value)[0]; IssueBuffer::maybeAdd( new InvalidClass( diff --git a/phpcs.xml b/phpcs.xml index aa41f8fa2e3..731482a19a6 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -10,7 +10,7 @@ * Configuration * ************************************************************************************************************** --> - + @@ -91,8 +91,6 @@ - - @@ -251,13 +249,11 @@ - - bin/* - src/Psalm/Internal/* - tests/* + + diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 748be83439b..4f2c25cff79 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,7 +1,7 @@ - + tags['variablesfrom'][0]]]> @@ -32,12 +32,6 @@ - - - - - - namespace]]> @@ -54,17 +48,19 @@ symbol, '::')]]> symbol, '::')]]> symbol, '\\')]]> - - - - - - - + + + + + + + + + @@ -75,6 +71,7 @@ + composer_class_loader->findFile($pluginClassName)]]> autoloader]]> localName, $offset)]]> @@ -84,6 +81,9 @@ + + + file_path, 'stub')]]> file_path, 'vendor')]]> @@ -94,7 +94,6 @@ - directory]]> @@ -195,7 +194,6 @@ description]]> var_id]]> - line_number]]> @@ -282,6 +280,11 @@ defining_fqcln]]> + + + + + @@ -457,8 +460,6 @@ - - @@ -540,7 +541,6 @@ self]]> - @@ -641,6 +641,7 @@ + @@ -652,7 +653,6 @@ calling_method_id]]> - value, '::')]]> value, '::')]]> @@ -935,7 +935,6 @@ - @@ -1099,9 +1098,9 @@ - + - + error_baseline]]> @@ -1303,6 +1302,18 @@ + + + + + + + + + + + + readEnv['CI_PR_NUMBER']]]> @@ -1358,6 +1369,9 @@ + + + @@ -1372,7 +1386,23 @@ + + + + + + + + + + + + + + + + TCPServerAddress]]> TCPServerAddress]]> @@ -1409,7 +1439,6 @@ - parser->parse( $hacky_class_fix, $error_handler, @@ -1427,7 +1456,6 @@ children[1]]]> - @@ -1436,6 +1464,9 @@ + + + newModifier]]> @@ -1481,9 +1512,16 @@ - - 0]]> - + + + + + , string>]]> + + + + + @@ -1562,32 +1600,6 @@ start_change]]> - - - - - - getArgument('pluginName')]]> - getOption('config')]]> - - - - - - - - getArgument('pluginName')]]> - getOption('config')]]> - - - - - - - - getOption('config')]]> - - @@ -1760,9 +1772,6 @@ - - - properties[0]]]> properties[0]]]> @@ -2099,11 +2108,6 @@ - - - - - @@ -2135,11 +2139,6 @@ - - - - - @@ -2152,11 +2151,6 @@ - - - getGenericValueType())]]> - getGenericValueType())]]> - @@ -2179,18 +2173,6 @@ properties[0]]]> properties[0]]]> - - - - - - - - - - - type_param]]> - @@ -2204,14 +2186,6 @@ - - - - - - - - @@ -2242,11 +2216,6 @@ - - - extra_types]]> - - @@ -2320,6 +2289,29 @@ + + + composer_lock]]> + composer_lock]]> + composer_lock]]> + composer_lock]]> + composer_lock]]> + composer_lock]]> + composer_lock]]> + composer_lock]]> + composer_lock]]> + composer_lock]]> + config_file]]> + config_file]]> + config_file]]> + config_file]]> + config_file]]> + config_file]]> + config_file]]> + config_file]]> + config_file]]> + + @@ -2348,6 +2340,16 @@ + + + + + + + + + + @@ -2357,9 +2359,4 @@ - - - - - diff --git a/psalm.xml.dist b/psalm.xml.dist index 0843b86bcc9..9633b120fdf 100644 --- a/psalm.xml.dist +++ b/psalm.xml.dist @@ -12,7 +12,8 @@ limitMethodComplexity="true" errorBaseline="psalm-baseline.xml" findUnusedPsalmSuppress="true" - findUnusedBaselineEntry="false" + findUnusedBaselineEntry="true" + findUnusedIssueHandlerSuppression="true" > @@ -63,24 +64,6 @@ - - - - - - - - - - - - - - - - - - @@ -104,12 +87,6 @@ - - - - - - @@ -167,11 +144,32 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Psalm/Aliases.php b/src/Psalm/Aliases.php index 103d3f449d0..9471120d03e 100644 --- a/src/Psalm/Aliases.php +++ b/src/Psalm/Aliases.php @@ -1,5 +1,7 @@ - */ - public $uses; - - /** - * @var array - */ - public $uses_flipped; - - /** - * @var array - */ - public $functions; - - /** - * @var array - */ - public $functions_flipped; - - /** - * @var array - */ - public $constants; - - /** - * @var array - */ - public $constants_flipped; - - /** @var string|null */ - public $namespace; - - /** @var ?int */ - public $namespace_first_stmt_start; + public ?int $namespace_first_stmt_start = null; - /** @var ?int */ - public $uses_start; + public ?int $uses_start = null; - /** @var ?int */ - public $uses_end; + public ?int $uses_end = null; /** * @param array $uses @@ -61,20 +27,13 @@ final class Aliases * @psalm-mutation-free */ public function __construct( - ?string $namespace = null, - array $uses = [], - array $functions = [], - array $constants = [], - array $uses_flipped = [], - array $functions_flipped = [], - array $constants_flipped = [] + public ?string $namespace = null, + public array $uses = [], + public array $functions = [], + public array $constants = [], + public array $uses_flipped = [], + public array $functions_flipped = [], + public array $constants_flipped = [], ) { - $this->namespace = $namespace; - $this->uses = $uses; - $this->functions = $functions; - $this->constants = $constants; - $this->uses_flipped = $uses_flipped; - $this->functions_flipped = $functions_flipped; - $this->constants_flipped = $constants_flipped; } } diff --git a/src/Psalm/CodeLocation.php b/src/Psalm/CodeLocation.php index 1d80ef71912..e9041cffb2e 100644 --- a/src/Psalm/CodeLocation.php +++ b/src/Psalm/CodeLocation.php @@ -1,5 +1,7 @@ file_start = (int)$stmt->getAttribute('startFilePos'); @@ -261,49 +251,24 @@ private function calculateRealLocation(): void $indentation = (int)strpos($key_line, '@'); - $key_line = trim(preg_replace('@\**/\s*@', '', mb_strcut($key_line, $indentation))); + $key_line = trim((string) preg_replace('@\**/\s*@', '', mb_strcut($key_line, $indentation))); $this->selection_start = $preview_offset + $indentation + $this->preview_start; $this->selection_end = $this->selection_start + strlen($key_line); } if ($this->regex_type !== null) { - switch ($this->regex_type) { - case self::VAR_TYPE: - $regex = '/@(?:psalm-)?var[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/'; - break; - - case self::FUNCTION_RETURN_TYPE: - $regex = '/\\:\s+(\\??\s*[A-Za-z0-9_\\\\\[\]]+)/'; - break; - - case self::FUNCTION_PARAM_TYPE: - $regex = '/^(\\??\s*[A-Za-z0-9_\\\\\[\]]+)\s/'; - break; - - case self::FUNCTION_PHPDOC_RETURN_TYPE: - $regex = '/@(?:psalm-)?return[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/'; - break; - - case self::FUNCTION_PHPDOC_METHOD: - $regex = '/@(?:psalm-)?method[ \t]+(.*)/'; - break; - - case self::FUNCTION_PHPDOC_PARAM_TYPE: - $regex = '/@(?:psalm-)?param[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/'; - break; - - case self::FUNCTION_PARAM_VAR: - $regex = '/(\$[^ ]*)/'; - break; - - case self::CATCH_VAR: - $regex = '/(\$[^ ^\)]*)/'; - break; - - default: - throw new UnexpectedValueException('Unrecognised regex type ' . $this->regex_type); - } + $regex = match ($this->regex_type) { + self::VAR_TYPE => '/@(?:psalm-)?var[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/', + self::FUNCTION_RETURN_TYPE => '/\\:\s+(\\??\s*[A-Za-z0-9_\\\\\[\]]+)/', + self::FUNCTION_PARAM_TYPE => '/^(\\??\s*[A-Za-z0-9_\\\\\[\]]+)\s/', + self::FUNCTION_PHPDOC_RETURN_TYPE => '/@(?:psalm-)?return[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/', + self::FUNCTION_PHPDOC_METHOD => '/@(?:psalm-)?method[ \t]+(.*)/', + self::FUNCTION_PHPDOC_PARAM_TYPE => '/@(?:psalm-)?param[ \t]+' . CommentAnalyzer::TYPE_REGEX . '/', + self::FUNCTION_PARAM_VAR => '/(\$[^ ]*)/', + self::CATCH_VAR => '/(\$[^ ^\)]*)/', + default => throw new UnexpectedValueException('Unrecognised regex type ' . $this->regex_type), + }; $preview_snippet = mb_strcut( $file_contents, diff --git a/src/Psalm/CodeLocation/DocblockTypeLocation.php b/src/Psalm/CodeLocation/DocblockTypeLocation.php index f38c79f01ce..36a76aef192 100644 --- a/src/Psalm/CodeLocation/DocblockTypeLocation.php +++ b/src/Psalm/CodeLocation/DocblockTypeLocation.php @@ -1,18 +1,20 @@ file_start = $file_start; // matches how CodeLocation works diff --git a/src/Psalm/CodeLocation/ParseErrorLocation.php b/src/Psalm/CodeLocation/ParseErrorLocation.php index 1714ff7fead..911371c284b 100644 --- a/src/Psalm/CodeLocation/ParseErrorLocation.php +++ b/src/Psalm/CodeLocation/ParseErrorLocation.php @@ -1,5 +1,7 @@ file_start = (int)$error->getAttributes()['startFilePos']; - /** @psalm-suppress PossiblyUndefinedStringArrayOffset, ImpureMethodCall */ + /** @psalm-suppress PossiblyUndefinedStringArrayOffset */ $this->file_end = (int)$error->getAttributes()['endFilePos']; $this->raw_file_start = $this->file_start; $this->raw_file_end = $this->file_end; diff --git a/src/Psalm/CodeLocation/Raw.php b/src/Psalm/CodeLocation/Raw.php index 30d68d2f900..bcafcaa7d25 100644 --- a/src/Psalm/CodeLocation/Raw.php +++ b/src/Psalm/CodeLocation/Raw.php @@ -1,5 +1,7 @@ file_start = $file_start; $this->file_end = $file_end; diff --git a/src/Psalm/Codebase.php b/src/Psalm/Codebase.php index dc0bcf03910..288de79102a 100644 --- a/src/Psalm/Codebase.php +++ b/src/Psalm/Codebase.php @@ -1,5 +1,7 @@ > */ - public $use_referencing_locations = []; + public array $use_referencing_locations = []; - /** - * @var FileStorageProvider - */ - public $file_storage_provider; + public FileStorageProvider $file_storage_provider; - /** - * @var ClassLikeStorageProvider - */ - public $classlike_storage_provider; + public ClassLikeStorageProvider $classlike_storage_provider; - /** - * @var bool - */ - public $collect_references = false; + public bool $collect_references = false; - /** - * @var bool - */ - public $collect_locations = false; + public bool $collect_locations = false; /** * @var null|'always'|'auto' */ - public $find_unused_code; + public ?string $find_unused_code = null; - /** - * @var FileProvider - */ - public $file_provider; + public FileProvider $file_provider; - /** - * @var FileReferenceProvider - */ - public $file_reference_provider; + public FileReferenceProvider $file_reference_provider; - /** - * @var StatementsProvider - */ - public $statements_provider; + public StatementsProvider $statements_provider; - private Progress $progress; + private readonly Progress $progress; /** * @var array */ - private static $stubbed_constants = []; + private static array $stubbed_constants = []; /** * Whether to register autoloaded information - * - * @var bool */ - public $register_autoload_files = false; + public bool $register_autoload_files = false; /** * Whether to log functions just at the file level or globally (for stubs) - * - * @var bool */ - public $register_stub_files = false; + public bool $register_stub_files = false; - /** - * @var bool - */ - public $find_unused_variables = false; + public bool $find_unused_variables = false; - /** - * @var Scanner - */ - public $scanner; + public Scanner $scanner; - /** - * @var Analyzer - */ - public $analyzer; + public Analyzer $analyzer; - /** - * @var Functions - */ - public $functions; + public Functions $functions; - /** - * @var ClassLikes - */ - public $classlikes; + public ClassLikes $classlikes; - /** - * @var Methods - */ - public $methods; + public Methods $methods; - /** - * @var Properties - */ - public $properties; + public Properties $properties; - /** - * @var Populator - */ - public $populator; + public Populator $populator; - /** - * @var ?TaintFlowGraph - */ - public $taint_flow_graph; + public ?TaintFlowGraph $taint_flow_graph = null; - /** - * @var bool - */ - public $server_mode = false; + public bool $server_mode = false; - /** - * @var bool - */ - public $store_node_types = false; + public bool $store_node_types = false; /** * Whether or not to infer types from usage. Computationally expensive, so turned off by default - * - * @var bool */ - public $infer_types_from_usage = false; + public bool $infer_types_from_usage = false; - /** - * @var bool - */ - public $alter_code = false; + public bool $alter_code = false; - /** - * @var bool - */ - public $diff_methods = false; + public bool $diff_methods = false; + + /** whether or not we only checked a part of the codebase */ + public bool $diff_run = false; /** * @var array */ - public $methods_to_move = []; + public array $methods_to_move = []; /** * @var array */ - public $methods_to_rename = []; + public array $methods_to_rename = []; /** * @var array */ - public $properties_to_move = []; + public array $properties_to_move = []; /** * @var array */ - public $properties_to_rename = []; + public array $properties_to_rename = []; /** * @var array */ - public $class_constants_to_move = []; + public array $class_constants_to_move = []; /** * @var array */ - public $class_constants_to_rename = []; + public array $class_constants_to_rename = []; /** * @var array */ - public $classes_to_move = []; + public array $classes_to_move = []; /** * @var array */ - public $call_transforms = []; + public array $call_transforms = []; /** * @var array */ - public $property_transforms = []; + public array $property_transforms = []; /** * @var array */ - public $class_constant_transforms = []; + public array $class_constant_transforms = []; /** * @var array */ - public $class_transforms = []; + public array $class_transforms = []; - /** - * @var bool - */ - public $allow_backwards_incompatible_changes = true; + public bool $allow_backwards_incompatible_changes = true; - /** @var int */ - public $analysis_php_version_id = PHP_VERSION_ID; + public int $analysis_php_version_id = PHP_VERSION_ID; /** @var 'cli'|'config'|'composer'|'tests'|'runtime' */ - public $php_version_source = 'runtime'; + public string $php_version_source = 'runtime'; - /** - * @var bool - */ - public $track_unused_suppressions = false; + public bool $track_unused_suppressions = false; /** @internal */ public function __construct( - Config $config, + public Config $config, Providers $providers, - ?Progress $progress = null + ?Progress $progress = null, ) { if ($progress === null) { $progress = new VoidProgress(); } - - $this->config = $config; $this->file_storage_provider = $providers->file_storage_provider; $this->classlike_storage_provider = $providers->classlike_storage_provider; $this->progress = $progress; @@ -580,11 +511,11 @@ public function findReferencesToSymbol(string $symbol): array throw new UnexpectedValueException('Should not be checking references'); } - if (strpos($symbol, '::$') !== false) { + if (str_contains($symbol, '::$')) { return $this->findReferencesToProperty($symbol); } - if (strpos($symbol, '::') !== false) { + if (str_contains($symbol, '::')) { return $this->findReferencesToMethod($symbol); } @@ -672,7 +603,7 @@ public function classOrInterfaceExists( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { return $this->classlikes->classOrInterfaceExists( $fq_class_name, @@ -691,7 +622,7 @@ public function classOrInterfaceOrEnumExists( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { return $this->classlikes->classOrInterfaceOrEnumExists( $fq_class_name, @@ -715,7 +646,7 @@ public function classExists( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { return $this->classlikes->classExists( $fq_class_name, @@ -748,7 +679,7 @@ public function interfaceExists( string $fq_interface_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { return $this->classlikes->interfaceExists( $fq_interface_name, @@ -800,7 +731,7 @@ public function traitHasCorrectCasing(string $fq_trait_name): bool */ public function getFunctionLikeStorage( StatementsAnalyzer $statements_analyzer, - string $function_id + string $function_id, ): FunctionLikeStorage { $doesMethodExist = MethodIdentifier::isValidMethodIdReference($function_id) @@ -823,16 +754,13 @@ public function getFunctionLikeStorage( /** * Whether or not a given method exists - * - * @param string|MethodIdentifier $method_id - * @param string|MethodIdentifier|null $calling_method_id */ public function methodExists( - $method_id, + string|MethodIdentifier $method_id, ?CodeLocation $code_location = null, - $calling_method_id = null, + string|MethodIdentifier|null $calling_method_id = null, ?string $file_path = null, - bool $is_used = true + bool $is_used = true, ): bool { return $this->methods->methodExists( MethodIdentifier::wrap($method_id), @@ -846,28 +774,26 @@ public function methodExists( } /** - * @param string|MethodIdentifier $method_id * @return array */ - public function getMethodParams($method_id): array + public function getMethodParams(string|MethodIdentifier $method_id): array { return $this->methods->getMethodParams(MethodIdentifier::wrap($method_id)); } - /** - * @param string|MethodIdentifier $method_id - */ - public function isVariadic($method_id): bool + public function isVariadic(string|MethodIdentifier $method_id): bool { return $this->methods->isVariadic(MethodIdentifier::wrap($method_id)); } /** - * @param string|MethodIdentifier $method_id * @param list $call_args */ - public function getMethodReturnType($method_id, ?string &$self_class, array $call_args = []): ?Union - { + public function getMethodReturnType( + string|MethodIdentifier $method_id, + ?string &$self_class, + array $call_args = [], + ): ?Union { return $this->methods->getMethodReturnType( MethodIdentifier::wrap($method_id), $self_class, @@ -876,20 +802,14 @@ public function getMethodReturnType($method_id, ?string &$self_class, array $cal ); } - /** - * @param string|MethodIdentifier $method_id - */ - public function getMethodReturnsByRef($method_id): bool + public function getMethodReturnsByRef(string|MethodIdentifier $method_id): bool { return $this->methods->getMethodReturnsByRef(MethodIdentifier::wrap($method_id)); } - /** - * @param string|MethodIdentifier $method_id - */ public function getMethodReturnTypeLocation( - $method_id, - ?CodeLocation &$defined_location = null + string|MethodIdentifier $method_id, + ?CodeLocation &$defined_location = null, ): ?CodeLocation { return $this->methods->getMethodReturnTypeLocation( MethodIdentifier::wrap($method_id), @@ -897,10 +817,7 @@ public function getMethodReturnTypeLocation( ); } - /** - * @param string|MethodIdentifier $method_id - */ - public function getDeclaringMethodId($method_id): ?string + public function getDeclaringMethodId(string|MethodIdentifier $method_id): ?string { $new_method_id = $this->methods->getDeclaringMethodId(MethodIdentifier::wrap($method_id)); @@ -909,10 +826,8 @@ public function getDeclaringMethodId($method_id): ?string /** * Get the class this method appears in (vs is declared in, which could give a trait) - * - * @param string|MethodIdentifier $method_id */ - public function getAppearingMethodId($method_id): ?string + public function getAppearingMethodId(string|MethodIdentifier $method_id): ?string { $new_method_id = $this->methods->getAppearingMethodId(MethodIdentifier::wrap($method_id)); @@ -920,18 +835,14 @@ public function getAppearingMethodId($method_id): ?string } /** - * @param string|MethodIdentifier $method_id * @return array */ - public function getOverriddenMethodIds($method_id): array + public function getOverriddenMethodIds(string|MethodIdentifier $method_id): array { return $this->methods->getOverriddenMethodIds(MethodIdentifier::wrap($method_id)); } - /** - * @param string|MethodIdentifier $method_id - */ - public function getCasedMethodId($method_id): string + public function getCasedMethodId(string|MethodIdentifier $method_id): string { return $this->methods->getCasedMethodId(MethodIdentifier::wrap($method_id)); } @@ -942,7 +853,7 @@ public function invalidateInformationForFile(string $file_path): void try { $file_storage = $this->file_storage_provider->get($file_path); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return; } @@ -988,12 +899,12 @@ public function getFunctionStorageForSymbol(string $file_path, string $symbol): * Get Markup content from Reference */ public function getMarkupContentForSymbolByReference( - Reference $reference + Reference $reference, ): ?PHPMarkdownContent { //Direct Assignment if (is_numeric($reference->symbol[0])) { return new PHPMarkdownContent( - preg_replace( + (string) preg_replace( '/^[^:]*:/', '', $reference->symbol, @@ -1031,8 +942,8 @@ public function getMarkupContentForSymbolByReference( [, $symbol_name] = explode('::', $reference->symbol); //Class Property - if (strpos($reference->symbol, '$') !== false) { - $property_id = preg_replace('/^\\\\/', '', $reference->symbol); + if (str_contains($reference->symbol, '$')) { + $property_id = (string) preg_replace('/^\\\\/', '', $reference->symbol); /** @psalm-suppress PossiblyUndefinedIntArrayOffset */ [$fq_class_name, $property_name] = explode('::$', $property_id); $class_storage = $this->classlikes->getStorageFor($fq_class_name); @@ -1127,7 +1038,7 @@ public function getMarkupContentForSymbolByReference( } //Procedural Variable - if (strpos($reference->symbol, '$') === 0) { + if (str_starts_with($reference->symbol, '$')) { $type = VariableFetchAnalyzer::getGlobalType($reference->symbol, $this->analysis_php_version_id); if (!$type->isMixed()) { return new PHPMarkdownContent( @@ -1148,7 +1059,7 @@ public function getMarkupContentForSymbolByReference( $storage->name, $storage->description, ); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { //continue on as normal } @@ -1201,228 +1112,10 @@ public function getMarkupContentForSymbolByReference( return new PHPMarkdownContent($reference->symbol); } - /** - * @psalm-suppress PossiblyUnusedMethod - * @deprecated will be removed in Psalm 6. use {@see Codebase::getSymbolLocationByReference()} instead - */ - public function getSymbolInformation(string $file_path, string $symbol): ?array - { - if (is_numeric($symbol[0])) { - return ['type' => preg_replace('/^[^:]*:/', '', $symbol)]; - } - - try { - if (strpos($symbol, '::')) { - if (strpos($symbol, '()')) { - $symbol = substr($symbol, 0, -2); - - /** @psalm-suppress ArgumentTypeCoercion */ - $method_id = new MethodIdentifier(...explode('::', $symbol)); - - $declaring_method_id = $this->methods->getDeclaringMethodId($method_id); - - if (!$declaring_method_id) { - return null; - } - - $storage = $this->methods->getStorage($declaring_method_id); - - return [ - 'type' => 'getCompletionSignature(), - 'description' => $storage->description, - ]; - } - - [, $symbol_name] = explode('::', $symbol); - - if (strpos($symbol, '$') !== false) { - $storage = $this->properties->getStorage($symbol); - - return [ - 'type' => 'getInfo() . ' ' . $symbol_name, - 'description' => $storage->description, - ]; - } - - [$fq_classlike_name, $const_name] = explode('::', $symbol); - - $class_constants = $this->classlikes->getConstantsForClass( - $fq_classlike_name, - ReflectionProperty::IS_PRIVATE, - ); - - if (!isset($class_constants[$const_name])) { - return null; - } - - return [ - 'type' => ' $class_constants[$const_name]->description, - ]; - } - - if (strpos($symbol, '()')) { - $function_id = strtolower(substr($symbol, 0, -2)); - $file_storage = $this->file_storage_provider->get($file_path); - - if (isset($file_storage->functions[$function_id])) { - $function_storage = $file_storage->functions[$function_id]; - - return [ - 'type' => 'getCompletionSignature(), - 'description' => $function_storage->description, - ]; - } - - if (!$function_id) { - return null; - } - - $function = $this->functions->getStorage(null, $function_id); - return [ - 'type' => 'getCompletionSignature(), - 'description' => $function->description, - ]; - } - - if (strpos($symbol, '$') === 0) { - $type = VariableFetchAnalyzer::getGlobalType($symbol, $this->analysis_php_version_id); - if (!$type->isMixed()) { - return ['type' => 'classlike_storage_provider->get($symbol); - return [ - 'type' => 'abstract ? 'abstract ' : '') . 'class ' . $storage->name, - 'description' => $storage->description, - ]; - } catch (InvalidArgumentException $e) { - } - - if (strpos($symbol, '\\')) { - $const_name_parts = explode('\\', $symbol); - $const_name = array_pop($const_name_parts); - $namespace_name = implode('\\', $const_name_parts); - - $namespace_constants = NamespaceAnalyzer::getConstantsForNamespace( - $namespace_name, - ReflectionProperty::IS_PUBLIC, - ); - if (isset($namespace_constants[$const_name])) { - $type = $namespace_constants[$const_name]; - return ['type' => 'file_storage_provider->get($file_path); - if (isset($file_storage->constants[$symbol])) { - return ['type' => 'constants[$symbol]]; - } - $constant = ConstFetchAnalyzer::getGlobalConstType($this, $symbol, $symbol); - - if ($constant) { - return ['type' => 'getMessage()); - - return null; - } - } - - /** - * @psalm-suppress PossiblyUnusedMethod - * @deprecated will be removed in Psalm 6. use {@see Codebase::getSymbolLocationByReference()} instead - */ - public function getSymbolLocation(string $file_path, string $symbol): ?CodeLocation - { - if (is_numeric($symbol[0])) { - $symbol = preg_replace('/:.*/', '', $symbol); - $symbol_parts = explode('-', $symbol); - - $file_contents = $this->getFileContents($file_path); - - return new Raw( - $file_contents, - $file_path, - $this->config->shortenFileName($file_path), - (int) $symbol_parts[0], - (int) $symbol_parts[1], - ); - } - - try { - if (strpos($symbol, '::')) { - if (strpos($symbol, '()')) { - $symbol = substr($symbol, 0, -2); - - /** @psalm-suppress ArgumentTypeCoercion */ - $method_id = new MethodIdentifier(...explode('::', $symbol)); - - $declaring_method_id = $this->methods->getDeclaringMethodId($method_id); - - if (!$declaring_method_id) { - return null; - } - - $storage = $this->methods->getStorage($declaring_method_id); - - return $storage->location; - } - - if (strpos($symbol, '$') !== false) { - $storage = $this->properties->getStorage($symbol); - - return $storage->location; - } - - [$fq_classlike_name, $const_name] = explode('::', $symbol); - - $class_constants = $this->classlikes->getConstantsForClass( - $fq_classlike_name, - ReflectionProperty::IS_PRIVATE, - ); - - if (!isset($class_constants[$const_name])) { - return null; - } - - return $class_constants[$const_name]->location; - } - - if (strpos($symbol, '()')) { - $file_storage = $this->file_storage_provider->get($file_path); - - $function_id = strtolower(substr($symbol, 0, -2)); - - if (isset($file_storage->functions[$function_id])) { - return $file_storage->functions[$function_id]->location; - } - - if (!$function_id) { - return null; - } - - return $this->functions->getStorage(null, $function_id)->location; - } - - return $this->classlike_storage_provider->get($symbol)->location; - } catch (UnexpectedValueException $e) { - error_log($e->getMessage()); - - return null; - } catch (InvalidArgumentException $e) { - return null; - } - } - public function getSymbolLocationByReference(Reference $reference): ?CodeLocation { if (is_numeric($reference->symbol[0])) { - $symbol = preg_replace('/:.*/', '', $reference->symbol); + $symbol = (string) preg_replace('/:.*/', '', $reference->symbol); $symbol_parts = explode('-', $symbol); if (!isset($symbol_parts[0]) || !isset($symbol_parts[1])) { @@ -1463,7 +1156,7 @@ public function getSymbolLocationByReference(Reference $reference): ?CodeLocatio return $storage->location; } - if (strpos($reference->symbol, '$') !== false) { + if (str_contains($reference->symbol, '$')) { $storage = $this->properties->getStorage( $reference->symbol, ); @@ -1515,13 +1208,12 @@ public function getSymbolLocationByReference(Reference $reference): ?CodeLocatio error_log($e->getMessage()); return null; - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return null; } } /** - * @psalm-suppress PossiblyUnusedMethod * @return array{0: string, 1: Range}|null */ public function getReferenceAtPosition(string $file_path, Position $position): ?array @@ -1538,7 +1230,7 @@ public function getReferenceAtPosition(string $file_path, Position $position): ? */ public function getReferenceAtPositionAsReference( string $file_path, - Position $position + Position $position, ): ?Reference { $is_open = $this->file_provider->isOpen($file_path); @@ -1651,11 +1343,11 @@ public function getFunctionArgumentAtPosition(string $file_path, Position $posit */ public function getSignatureInformation( string $function_symbol, - ?string $file_path = null + ?string $file_path = null, ): ?SignatureInformation { $signature_label = ''; $signature_documentation = null; - if (strpos($function_symbol, '::') !== false) { + if (str_contains($function_symbol, '::')) { /** @psalm-suppress ArgumentTypeCoercion */ $method_id = new MethodIdentifier(...explode('::', $function_symbol)); @@ -1684,7 +1376,7 @@ public function getSignatureInformation( $params = $function_storage->params; $signature_label = $function_storage->cased_name; $signature_documentation = $function_storage->description; - } catch (Exception $exception) { + } catch (Exception) { if (InternalCallMapHandler::inCallMap($function_symbol)) { $callables = InternalCallMapHandler::getCallablesFromCallMap($function_symbol); @@ -1877,7 +1569,7 @@ public function getCompletionItemsForClassishThing( string $gap, bool $snippets_supported = false, ?array $allow_visibilities = null, - array $ignore_fq_class_names = [] + array $ignore_fq_class_names = [], ): array { if ($allow_visibilities === null) { $allow_visibilities = [ @@ -1912,10 +1604,17 @@ public function getCompletionItemsForClassishThing( $method_storages += $class_storage->pseudo_static_methods; } + $had = []; foreach ($method_storages as $method_storage) { if (!in_array($method_storage->visibility, $allow_visibilities)) { continue; } + if ($method_storage->cased_name !== null) { + if (array_key_exists($method_storage->cased_name, $had)) { + continue; + } + $had[$method_storage->cased_name] = true; + } if ($method_storage->is_static || $gap === '->') { $completion_item = new CompletionItem( $method_storage->cased_name, @@ -2050,7 +1749,7 @@ public function filterCompletionItemsByBeginLiteralPart(array $items, string $li $res = []; foreach ($items as $item) { - if ($item->insertText && strpos($item->insertText, $literal_part) === 0) { + if ($item->insertText && str_starts_with($item->insertText, $literal_part)) { $res[] = $item; } } @@ -2064,7 +1763,7 @@ public function filterCompletionItemsByBeginLiteralPart(array $items, string $li public function getCompletionItemsForPartialSymbol( string $type_string, int $offset, - string $file_path + string $file_path, ): array { $fq_suggestion = false; @@ -2083,7 +1782,7 @@ public function getCompletionItemsForPartialSymbol( foreach ($file_storage->classlikes_in_file as $fq_class_name => $_) { try { $class_storage = $this->classlike_storage_provider->get($fq_class_name); - } catch (Exception $e) { + } catch (Exception) { continue; } @@ -2130,7 +1829,7 @@ public function getCompletionItemsForPartialSymbol( ) { $file_contents = $this->getFileContents($file_path); - $class_name = preg_replace('/^.*\\\/', '', $fq_class_name, 1); + $class_name = (string) preg_replace('/^.*\\\/', '', $fq_class_name, 1); if ($aliases->uses_end) { $position = self::getPositionFromOffset($aliases->uses_end, $file_contents); @@ -2158,7 +1857,7 @@ public function getCompletionItemsForPartialSymbol( try { $class_storage = $this->classlike_storage_provider->get($fq_class_name); $description = $class_storage->description; - } catch (Exception $e) { + } catch (Exception) { $description = null; } @@ -2198,14 +1897,14 @@ public function getCompletionItemsForPartialSymbol( } $in_namespace_map = false; foreach ($namespace_map as $namespace_name => $namespace_alias) { - if (strpos($function_lowercase, $namespace_name . '\\') === 0) { + if (str_starts_with($function_lowercase, $namespace_name . '\\')) { $function_name = $namespace_alias . '\\' . substr($function_name, strlen($namespace_name) + 1); $in_namespace_map = true; } } // If the function is not use'd, and it's not a global function // prepend it with a backslash. - if (!$in_namespace_map && strpos($function_name, '\\') !== false) { + if (!$in_namespace_map && str_contains($function_name, '\\')) { $function_name = '\\' . $function_name; } $completion_items[] = new CompletionItem( @@ -2287,7 +1986,7 @@ public function getCompletionItemsForType(Union $type): array * @return list */ public function getCompletionItemsForArrayKeys( - string $type_string + string $type_string, ): array { $completion_items = []; $type = Type::parseString($type_string); @@ -2356,7 +2055,7 @@ public function isTypeContainedByType( bool $ignore_null = false, bool $ignore_false = false, bool $allow_interface_equality = false, - bool $allow_float_int_equality = true + bool $allow_float_int_equality = true, ): bool { return UnionTypeComparator::isContainedBy( $this, @@ -2385,7 +2084,7 @@ public function isTypeContainedByType( */ public function canTypeBeContainedByType( Union $input_type, - Union $container_type + Union $container_type, ): bool { return UnionTypeComparator::canBeContainedBy($this, $input_type, $container_type); } @@ -2424,20 +2123,19 @@ public function queueClassLikeForScanning( string $fq_classlike_name, bool $analyze_too = false, bool $store_failure = true, - array $phantom_classes = [] + array $phantom_classes = [], ): void { $this->scanner->queueClassLikeForScanning($fq_classlike_name, $analyze_too, $store_failure, $phantom_classes); } /** * @param array $taints - * @psalm-suppress PossiblyUnusedMethod */ public function addTaintSource( Union $expr_type, string $taint_id, array $taints = TaintKindGroup::ALL_INPUT, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): Union { if (!$this->taint_flow_graph) { return $expr_type; @@ -2458,12 +2156,11 @@ public function addTaintSource( /** * @param array $taints - * @psalm-suppress PossiblyUnusedMethod */ public function addTaintSink( string $taint_id, array $taints = TaintKindGroup::ALL_INPUT, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): void { if (!$this->taint_flow_graph) { return; diff --git a/src/Psalm/Config.php b/src/Psalm/Config.php index 93fecd99d7d..f5a325d1216 100644 --- a/src/Psalm/Config.php +++ b/src/Psalm/Config.php @@ -1,5 +1,7 @@ */ - public static $ERROR_LEVELS = [ + public static array $ERROR_LEVELS = [ self::REPORT_INFO, self::REPORT_ERROR, self::REPORT_SUPPRESS, @@ -159,7 +163,6 @@ class Config 'MixedArrayTypeCoercion', 'MixedAssignment', 'MixedFunctionCall', - 'MixedInferredReturnType', 'MixedMethodCall', 'MixedOperand', 'MixedPropertyFetch', @@ -176,7 +179,7 @@ class Config * * @var array */ - protected $universal_object_crates; + private array $universal_object_crates; /** * @var static|null @@ -185,74 +188,55 @@ class Config /** * Whether or not to use types as defined in docblocks - * - * @var bool */ - public $use_docblock_types = true; + public bool $use_docblock_types = true; /** * Whether or not to use types as defined in property docblocks. * This is distinct from the above because you may want to use * property docblocks, but not function docblocks. - * - * @var bool */ - public $use_docblock_property_types = false; + public bool $use_docblock_property_types = false; /** * Whether using property annotations in docblocks should implicitly seal properties - * - * @var bool */ - public $docblock_property_types_seal_properties = true; + public bool $docblock_property_types_seal_properties = true; /** * Whether or not to throw an exception on first error - * - * @var bool */ - public $throw_exception = false; + public bool $throw_exception = false; /** * The directory to store PHP Parser (and other) caches * * @internal - * @var string|null */ - public $cache_directory; + public ?string $cache_directory = null; private bool $cache_directory_initialized = false; /** * The directory to store all Psalm project caches - * - * @var string|null */ - public $global_cache_directory; + public ?string $global_cache_directory = null; /** * Path to the autoader - * - * @var string|null */ - public $autoloader; + public ?string $autoloader = null; - /** - * @var ProjectFileFilter|null - */ - protected $project_files; + protected ?ProjectFileFilter $project_files = null; - /** - * @var ProjectFileFilter|null - */ - protected $extra_files; + private ?ProjectFileFilter $extra_files = null; /** * The base directory of this config file without trailing slash - * - * @var string */ - public $base_dir; + public string $base_dir; + + public ?string $source_filename = null; /** * The PHP version to assume as declared in the config file @@ -304,240 +288,128 @@ class Config */ private array $stub_files = []; - /** - * @var bool - */ - public $hide_external_errors = false; + public bool $hide_external_errors = false; - /** - * @var bool - */ - public $hide_all_errors_except_passed_files = false; + public bool $hide_all_errors_except_passed_files = false; - /** @var bool */ - public $allow_includes = true; + public bool $allow_includes = true; /** @var 1|2|3|4|5|6|7|8 */ - public $level = 1; + public int $level = 1; - /** - * @var ?bool - */ - public $show_mixed_issues; + public ?bool $show_mixed_issues = null; - /** @var bool */ - public $strict_binary_operands = false; + public bool $strict_binary_operands = false; - /** - * @var bool - */ - public $remember_property_assignments_after_call = true; + public bool $remember_property_assignments_after_call = true; - /** @var bool */ - public $use_igbinary = false; + public bool $use_igbinary = false; /** @var 'lz4'|'deflate'|'off' */ - public $compressor = 'off'; + public string $compressor = 'off'; - /** - * @var bool - */ - public $allow_string_standin_for_class = false; + public bool $allow_string_standin_for_class = false; - /** - * @var bool - */ - public $disable_suppress_all = false; + public bool $disable_suppress_all = false; - /** - * @var bool - */ - public $use_phpdoc_method_without_magic_or_parent = false; + public bool $use_phpdoc_method_without_magic_or_parent = false; - /** - * @var bool - */ - public $use_phpdoc_property_without_magic_or_parent = false; + public bool $use_phpdoc_property_without_magic_or_parent = false; - /** - * @var bool - */ - public $skip_checks_on_unresolvable_includes = false; + public bool $skip_checks_on_unresolvable_includes = false; - /** - * @var bool - */ - public $seal_all_methods = false; + public bool $seal_all_methods = false; - /** - * @var bool - */ - public $seal_all_properties = false; + public bool $seal_all_properties = false; - /** - * @var bool - */ - public $memoize_method_calls = false; + public bool $memoize_method_calls = false; - /** - * @var bool - */ - public $hoist_constants = false; + public bool $hoist_constants = false; - /** - * @var bool - */ - public $add_param_default_to_docblock_type = false; + public bool $add_param_default_to_docblock_type = false; - /** - * @var bool - */ - public $disable_var_parsing = false; + public bool $disable_var_parsing = false; - /** - * @var bool - */ - public $check_for_throws_docblock = false; + public bool $check_for_throws_docblock = false; - /** - * @var bool - */ - public $check_for_throws_in_global_scope = false; + public bool $check_for_throws_in_global_scope = false; - /** - * @var bool - */ - public $ignore_internal_falsable_issues = true; + public bool $ignore_internal_falsable_issues = false; - /** - * @var bool - */ - public $ignore_internal_nullable_issues = true; + public bool $ignore_internal_nullable_issues = false; /** * @var array */ - public $ignored_exceptions = []; + public array $ignored_exceptions = []; /** * @var array */ - public $ignored_exceptions_in_global_scope = []; + public array $ignored_exceptions_in_global_scope = []; /** * @var array */ - public $ignored_exceptions_and_descendants = []; + public array $ignored_exceptions_and_descendants = []; /** * @var array */ - public $ignored_exceptions_and_descendants_in_global_scope = []; + public array $ignored_exceptions_and_descendants_in_global_scope = []; - /** - * @var bool - */ - public $infer_property_types_from_constructor = true; + public bool $infer_property_types_from_constructor = true; - /** - * @var bool - */ - public $ensure_array_string_offsets_exist = false; + public bool $ensure_array_string_offsets_exist = false; - /** - * @var bool - */ - public $ensure_array_int_offsets_exist = false; + public bool $ensure_array_int_offsets_exist = false; - /** - * @var bool - */ - public $ensure_override_attribute = false; + public bool $ensure_override_attribute = false; /** * @var array */ - public $forbidden_functions = []; + public array $forbidden_functions = []; - /** - * TODO: Psalm 6: Update default to be true and remove warning. - * - * @var bool - */ - public $find_unused_code = false; + public bool $find_unused_code = true; - /** - * @var bool - */ - public $find_unused_variables = false; + public bool $find_unused_variables = false; - /** - * @var bool - */ - public $find_unused_psalm_suppress = false; + public bool $find_unused_psalm_suppress = false; - /** - * TODO: Psalm 6: Update default to be true and remove warning. - */ - public bool $find_unused_baseline_entry = false; + public bool $find_unused_baseline_entry = true; - /** - * @var bool - */ - public $run_taint_analysis = false; + public bool $find_unused_issue_handler_suppression = true; - /** @var bool */ - public $use_phpstorm_meta_path = true; + public bool $run_taint_analysis = false; - /** - * @var bool - */ - public $resolve_from_config_file = true; + public bool $use_phpstorm_meta_path = true; - /** - * @var bool - */ - public $restrict_return_types = false; + public bool $resolve_from_config_file = true; - /** - * @var bool - */ - public $limit_method_complexity = false; + public bool $restrict_return_types = false; - /** - * @var int - */ - public $max_graph_size = 200; + public bool $limit_method_complexity = false; - /** - * @var int - */ - public $max_avg_path_length = 70; + public int $max_graph_size = 200; - /** - * @var int - */ - public $max_shaped_array_size = 100; + public int $max_avg_path_length = 70; + + public int $max_shaped_array_size = 100; /** * @var string[] */ - public $plugin_paths = []; + public array $plugin_paths = []; /** * @var array */ private array $plugin_classes = []; - /** - * @var bool - */ - public $allow_internal_named_arg_calls = true; + public bool $allow_internal_named_arg_calls = true; - /** - * @var bool - */ - public $allow_named_arg_calls = true; + public bool $allow_named_arg_calls = true; /** @var array */ private array $predefined_constants = []; @@ -547,75 +419,51 @@ class Config private ?ClassLoader $composer_class_loader = null; - /** - * @var string - */ - public $hash = ''; - - /** @var string|null */ - public $error_baseline; + public string $hash = ''; - /** - * @var bool - */ - public $include_php_versions_in_error_baseline = false; + public ?string $error_baseline = null; - /** - * @var string - * @deprecated Please use {@see self::$shepherd_endpoint} instead. Property will be removed in Psalm 6. - */ - public $shepherd_host = 'shepherd.dev'; + public bool $include_php_versions_in_error_baseline = false; /** - * @var string * @internal */ - public $shepherd_endpoint = 'https://shepherd.dev/hooks/psalm/'; + public string $shepherd_endpoint = 'https://shepherd.dev/hooks/psalm'; /** * @var array */ - public $globals = []; + public array $globals = []; - /** - * @var int - */ - public $max_string_length = 1_000; + public int $max_string_length = 1_000; private ?IncludeCollector $include_collector = null; - /** - * @var TaintAnalysisFileFilter|null - */ - protected $taint_analysis_ignored_files; + private ?TaintAnalysisFileFilter $taint_analysis_ignored_files = null; /** * @var bool whether to emit a backtrace of emitted issues to stderr */ - public $debug_emitted_issues = false; + public bool $debug_emitted_issues = false; private bool $report_info = true; - /** - * @var EventDispatcher - */ - public $eventDispatcher; + public EventDispatcher $eventDispatcher; /** @var list */ - public $config_issues = []; + public array $config_issues = []; /** * @var 'default'|'never'|'always' */ - public $trigger_error_exits = 'default'; + public string $trigger_error_exits = 'default'; /** * @var string[] */ - public $internal_stubs = []; + public array $internal_stubs = []; - /** @var ?int */ - public $threads; + public ?int $threads = null; /** * A list of php extensions supported by Psalm. @@ -628,7 +476,7 @@ class Config * @psalm-readonly-allow-private-mutation * @var array */ - public $php_extensions = [ + public array $php_extensions = [ "apcu" => null, "decimal" => null, "dom" => null, @@ -656,7 +504,7 @@ class Config * @var list * @readonly */ - public $php_extensions_supported_by_psalm_callmaps = [ + public array $php_extensions_supported_by_psalm_callmaps = [ 'apache', 'bcmath', 'bzip2', @@ -721,7 +569,7 @@ class Config * * @var array */ - public $php_extensions_not_supported = []; + public array $php_extensions_not_supported = []; /** * @var array @@ -837,7 +685,7 @@ public static function loadFromXML( string $base_dir, string $file_contents, ?string $current_dir = null, - ?string $file_path = null + ?string $file_path = null, ): Config { if ($current_dir === null) { $current_dir = $base_dir; @@ -863,6 +711,7 @@ private static function loadDomDocument(string $base_dir, string $file_contents) $dom_document->loadXML($file_contents, LIBXML_NONET); $dom_document->xinclude(LIBXML_NOWARNING | LIBXML_NONET); + /** @psalm-suppress PossiblyFalseArgument */ chdir($oldpwd); return $dom_document; } @@ -952,7 +801,7 @@ private static function processDeprecatedAttribute( DOMAttr $attribute, string $file_contents, self $config, - string $config_path + string $config_path, ): void { $line = $attribute->getLineNo(); assert($line > 0); // getLineNo() always returns non-zero for nodes loaded from file @@ -978,7 +827,7 @@ private static function processDeprecatedElement( DOMElement $deprecated_element_xml, string $file_contents, self $config, - string $config_path + string $config_path, ): void { $line = $deprecated_element_xml->getLineNo(); assert($line > 0); @@ -1004,7 +853,7 @@ private static function processConfigDeprecations( self $config, DOMDocument $dom_document, string $file_contents, - string $config_path + string $config_path, ): void { $config->config_issues = []; @@ -1039,7 +888,6 @@ private static function processConfigDeprecations( /** * @param non-empty-string $file_contents * @psalm-suppress MixedAssignment - * @psalm-suppress MixedArgument * @psalm-suppress MixedPropertyFetch * @throws ConfigException */ @@ -1047,7 +895,7 @@ private static function fromXmlAndPaths( string $base_dir, string $file_contents, string $current_dir, - ?string $config_path + ?string $config_path, ): self { $config = new static(); @@ -1101,6 +949,7 @@ private static function fromXmlAndPaths( 'allowNamedArgumentCalls' => 'allow_named_arg_calls', 'findUnusedPsalmSuppress' => 'find_unused_psalm_suppress', 'findUnusedBaselineEntry' => 'find_unused_baseline_entry', + 'findUnusedIssueHandlerSuppression' => 'find_unused_issue_handler_suppression', 'reportInfo' => 'report_info', 'restrictReturnTypes' => 'restrict_return_types', 'limitMethodComplexity' => 'limit_method_complexity', @@ -1116,6 +965,7 @@ private static function fromXmlAndPaths( } } + $config->source_filename = $config_path; if ($config->resolve_from_config_file) { $config->base_dir = $base_dir; } else { @@ -1127,15 +977,17 @@ private static function fromXmlAndPaths( $composer_json = null; if (file_exists($composer_json_path)) { - $composer_json = json_decode(file_get_contents($composer_json_path), true); + $composer_json_contents = file_get_contents($composer_json_path); + assert($composer_json_contents !== false); + $composer_json = json_decode($composer_json_contents, true, 512, JSON_THROW_ON_ERROR); if (!is_array($composer_json)) { throw new UnexpectedValueException('Invalid composer.json at ' . $composer_json_path); } } $required_extensions = []; foreach (($composer_json["require"] ?? []) as $required => $_) { - if (strpos($required, "ext-") === 0) { - $required_extensions[strtolower(substr($required, 4))] = true; + if (str_starts_with((string) $required, "ext-")) { + $required_extensions[strtolower(substr((string) $required, 4))] = true; } } foreach ($required_extensions as $required_ext => $_) { @@ -1183,7 +1035,7 @@ private static function fromXmlAndPaths( } } - $config->autoloader = realpath($autoloader_path); + $config->autoloader = (string) realpath($autoloader_path); } if (isset($config_xml['cacheDirectory'])) { @@ -1236,18 +1088,10 @@ private static function fromXmlAndPaths( $config->compressor = 'deflate'; } - if (!isset($config_xml['findUnusedBaselineEntry'])) { - $config->config_warnings[] = '"findUnusedBaselineEntry" will default to "true" in Psalm 6.' - . ' You should explicitly enable or disable this setting.'; - } - if (isset($config_xml['findUnusedCode'])) { $attribute_text = (string) $config_xml['findUnusedCode']; $config->find_unused_code = $attribute_text === 'true' || $attribute_text === '1'; $config->find_unused_variables = $config->find_unused_code; - } else { - $config->config_warnings[] = '"findUnusedCode" will default to "true" in Psalm 6.' - . ' You should explicitly enable or disable this setting.'; } if (isset($config_xml['findUnusedVariablesAndParams'])) { @@ -1324,13 +1168,13 @@ private static function fromXmlAndPaths( } if ($paths_to_check !== null) { - $paths_to_add_to_project_files = array(); + $paths_to_add_to_project_files = []; foreach ($paths_to_check as $path) { // if we have an .xml arg here, the files passed are invalid // valid cases (in which we don't want to add CLI passed files to projectFiles though) // are e.g. if running phpunit tests for psalm itself - if (substr($path, -4) === '.xml') { - $paths_to_add_to_project_files = array(); + if (str_ends_with($path, '.xml')) { + $paths_to_add_to_project_files = []; break; } @@ -1353,14 +1197,14 @@ private static function fromXmlAndPaths( $paths_to_add_to_project_files[] = $prospective_path; } - if ($paths_to_add_to_project_files !== array() && !isset($config_xml->projectFiles)) { + if ($paths_to_add_to_project_files !== [] && !isset($config_xml->projectFiles)) { if ($config_xml === null) { $config_xml = new SimpleXMLElement(''); } $config_xml->addChild('projectFiles'); } - if ($paths_to_add_to_project_files !== array() && isset($config_xml->projectFiles)) { + if ($paths_to_add_to_project_files !== [] && isset($config_xml->projectFiles)) { foreach ($paths_to_add_to_project_files as $path) { if (is_dir($path)) { $child = $config_xml->projectFiles->addChild('directory'); @@ -1407,8 +1251,7 @@ private static function fromXmlAndPaths( if (isset($config_xml->universalObjectCrates) && isset($config_xml->universalObjectCrates->class)) { /** @var SimpleXMLElement $universal_object_crate */ foreach ($config_xml->universalObjectCrates->class as $universal_object_crate) { - /** @var string $classString */ - $classString = $universal_object_crate['name']; + $classString = (string) $universal_object_crate['name']; $config->addUniversalObjectCrate($classString); } } @@ -1554,6 +1397,12 @@ public function setComposerClassLoader(?ClassLoader $loader = null): void $this->composer_class_loader = $loader; } + /** @return array */ + public function getIssueHandlers(): array + { + return $this->issue_handlers; + } + public function setAdvancedErrorLevel(string $issue_key, array $config, ?string $default_error_level = null): void { $this->issue_handlers[$issue_key] = new IssueHandler(); @@ -1566,7 +1415,7 @@ public function setAdvancedErrorLevel(string $issue_key, array $config, ?string public function safeSetAdvancedErrorLevel( string $issue_key, array $config, - ?string $default_error_level = null + ?string $default_error_level = null, ): void { if (!isset($this->issue_handlers[$issue_key])) { $this->setAdvancedErrorLevel($issue_key, $config, $default_error_level); @@ -1592,7 +1441,7 @@ public function safeSetCustomErrorLevel(string $issue_key, string $error_level): private function loadFileExtensions(SimpleXMLElement $extensions): void { foreach ($extensions as $extension) { - $extension_name = preg_replace('/^\.?/', '', (string) $extension['name'], 1); + $extension_name = (string) preg_replace('/^\.?/', '', (string) $extension['name'], 1); $this->file_extensions[] = $extension_name; if (isset($extension['scanner'])) { @@ -1827,8 +1676,7 @@ private function getPluginClassForPath(Codebase $codebase, string $path, string public function shortenFileName(string $to): string { if (!is_file($to)) { - // if cwd is the root directory it will be just the directory separator - trim it off first - return preg_replace( + return (string) preg_replace( '/^' . preg_quote(rtrim($this->base_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR, '/') . '/', '', $to, @@ -1898,7 +1746,7 @@ public function reportIssueInFile(string $issue_type, string $file_path): bool try { $file_storage = $codebase->file_storage_provider->get($file_path); $dependent_files += $file_storage->required_by_file_paths; - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // do nothing } } @@ -1949,7 +1797,7 @@ public function trackTaintsInPath(string $file_path): bool public function getReportingLevelForIssue(CodeIssue $e): string { - $fqcn_parts = explode('\\', get_class($e)); + $fqcn_parts = explode('\\', $e::class); $issue_type = array_pop($fqcn_parts); $reporting_level = null; @@ -2014,17 +1862,17 @@ public static function getParentIssueType(string $issue_type): ?string return null; } - if (strpos($issue_type, 'Possibly') === 0) { - $stripped_issue_type = preg_replace('/^Possibly(False|Null)?/', '', $issue_type, 1); + if (str_starts_with($issue_type, 'Possibly')) { + $stripped_issue_type = (string) preg_replace('/^Possibly(False|Null)?/', '', $issue_type, 1); - if (strpos($stripped_issue_type, 'Invalid') === false && strpos($stripped_issue_type, 'Un') !== 0) { + if (!str_contains($stripped_issue_type, 'Invalid') && !str_starts_with($stripped_issue_type, 'Un')) { $stripped_issue_type = 'Invalid' . $stripped_issue_type; } return $stripped_issue_type; } - if (strpos($issue_type, 'Tainted') === 0) { + if (str_starts_with($issue_type, 'Tainted')) { return 'TaintedInput'; } @@ -2107,6 +1955,30 @@ public static function getParentIssueType(string $issue_type): ?string return null; } + /** @return array{type: string, index: int, count: int}[] */ + public function getIssueHandlerSuppressions(): array + { + $suppressions = []; + foreach ($this->issue_handlers as $key => $handler) { + foreach ($handler->getFilters() as $index => $filter) { + $suppressions[] = [ + 'type' => $key, + 'index' => $index, + 'count' => $filter->suppressions, + ]; + } + } + return $suppressions; + } + + /** @param array{type: string, index: int, count: int}[] $filters */ + public function combineIssueHandlerSuppressions(array $filters): void + { + foreach ($filters as $filter) { + $this->issue_handlers[$filter['type']]->getFilters()[$filter['index']]->suppressions += $filter['count']; + } + } + public function getReportingLevelForFile(string $issue_type, string $file_path): string { if (isset($this->issue_handlers[$issue_type])) { @@ -2157,7 +2029,7 @@ public function getReportingLevelForFunction(string $issue_type, string $functio if ($level === null && $issue_type === 'UndefinedFunction') { // undefined functions trigger global namespace fallback // so we should also check reporting levels for the symbol in global scope - $root_function_id = preg_replace('/.*\\\/', '', $function_id); + $root_function_id = (string) preg_replace('/.*\\\/', '', $function_id); if ($root_function_id !== $function_id) { /** @psalm-suppress PossiblyUndefinedStringArrayOffset https://github.com/vimeo/psalm/issues/7656 */ $level = $this->issue_handlers[$issue_type]->getReportingLevelForFunction($root_function_id); @@ -2331,7 +2203,7 @@ public function visitPreloadedStubFiles(Codebase $codebase, ?Progress $progress foreach ($stub_files as $file_path) { $file_path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file_path); // fix mangled phar paths on Windows - if (strpos($file_path, 'phar:\\\\') === 0) { + if (str_starts_with($file_path, 'phar:\\\\')) { $file_path = 'phar://'. substr($file_path, 7); } $codebase->scanner->addFileToDeepScan($file_path); @@ -2392,19 +2264,6 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null): } } - /** @deprecated Will be removed in Psalm 6 */ - $extensions_to_load_stubs_using_deprecated_way = ['apcu', 'random', 'redis']; - foreach ($extensions_to_load_stubs_using_deprecated_way as $ext_name) { - $ext_stub_path = $ext_stubs_dir . DIRECTORY_SEPARATOR . "$ext_name.phpstub"; - $is_stub_already_loaded = in_array($ext_stub_path, $this->internal_stubs, true); - $is_ext_explicitly_disabled = ($this->php_extensions[$ext_name] ?? null) === false; - if (! $is_stub_already_loaded && ! $is_ext_explicitly_disabled && extension_loaded($ext_name)) { - $this->internal_stubs[] = $ext_stub_path; - $this->config_warnings[] = "Psalm 6 will not automatically load stubs for ext-$ext_name." - . " You should explicitly enable or disable this ext in composer.json or Psalm config."; - } - } - foreach ($this->internal_stubs as $stub_path) { if (!file_exists($stub_path)) { throw new UnexpectedValueException('Cannot locate ' . $stub_path); @@ -2419,9 +2278,10 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null): if (is_file($phpstorm_meta_path)) { $stub_files[] = $phpstorm_meta_path; } elseif (is_dir($phpstorm_meta_path)) { - $phpstorm_meta_path = realpath($phpstorm_meta_path); + $phpstorm_meta_path = (string) realpath($phpstorm_meta_path); + $phpstorm_meta_files = glob($phpstorm_meta_path . '/*.meta.php', GLOB_NOSORT); - foreach (glob($phpstorm_meta_path . '/*.meta.php', GLOB_NOSORT) as $glob) { + foreach ($phpstorm_meta_files ?: [] as $glob) { if (is_file($glob) && realpath(dirname($glob)) === $phpstorm_meta_path) { $stub_files[] = $glob; } @@ -2432,7 +2292,7 @@ public function visitStubFiles(Codebase $codebase, ?Progress $progress = null): foreach ($stub_files as $file_path) { $file_path = str_replace(['/', '\\'], DIRECTORY_SEPARATOR, $file_path); // fix mangled phar paths on Windows - if (strpos($file_path, 'phar:\\\\') === 0) { + if (str_starts_with($file_path, 'phar:\\\\')) { $file_path = 'phar://' . substr($file_path, 7); } $codebase->scanner->addFileToDeepScan($file_path); @@ -2567,7 +2427,7 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P $codebase->classlikes->forgetMissingClassLikes(); $this->include_collector->runAndCollect( - [$this, 'requireAutoloader'], + $this->requireAutoloader(...), ); } @@ -2593,10 +2453,8 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P } } - /** - * @return string|false - */ - public function getComposerFilePathForClassLike(string $fq_classlike_name) + /** @return string|false */ + public function getComposerFilePathForClassLike(string $fq_classlike_name): string|bool { if (!$this->composer_class_loader) { return false; @@ -2636,7 +2494,7 @@ public function getPotentialComposerFilePathForClassLike(string $class): ?string && $this->isInProjectDirs($dir . DIRECTORY_SEPARATOR . 'testdummy.php') ) { $maxDepth = $depth; - $candidate_path = realpath($dir) . $pathEnd; + $candidate_path = (string) realpath($dir) . $pathEnd; } } } @@ -2771,8 +2629,10 @@ public function getPHPVersionFromComposerJson(): ?string if (file_exists($composer_json_path)) { try { - $composer_json = json_decode(file_get_contents($composer_json_path), true, 512, JSON_THROW_ON_ERROR); - } catch (JsonException $e) { + $composer_json_contents = file_get_contents($composer_json_path); + assert($composer_json_contents !== false); + $composer_json = json_decode($composer_json_contents, true, 512, JSON_THROW_ON_ERROR); + } catch (JsonException) { $composer_json = null; } diff --git a/src/Psalm/Config/Creator.php b/src/Psalm/Config/Creator.php index a2fe96463a5..2f88ade1042 100644 --- a/src/Psalm/Config/Creator.php +++ b/src/Psalm/Config/Creator.php @@ -1,5 +1,7 @@ '; @@ -284,11 +289,11 @@ private static function guessPhpFileDirs(string $current_dir): array $nodes = []; /** @var string[] */ - $php_files = array_merge( - glob($current_dir . DIRECTORY_SEPARATOR . '*.php', GLOB_NOSORT), - glob($current_dir . DIRECTORY_SEPARATOR . '**/*.php', GLOB_NOSORT), - glob($current_dir . DIRECTORY_SEPARATOR . '**/**/*.php', GLOB_NOSORT), - ); + $php_files = [ + ...glob($current_dir . DIRECTORY_SEPARATOR . '*.php', GLOB_NOSORT) ?: [], + ...glob($current_dir . DIRECTORY_SEPARATOR . '**/*.php', GLOB_NOSORT) ?: [], + ...glob($current_dir . DIRECTORY_SEPARATOR . '**/**/*.php', GLOB_NOSORT) ?: [], + ]; foreach ($php_files as $php_file) { $php_file = str_replace($current_dir . DIRECTORY_SEPARATOR, '', $php_file); diff --git a/src/Psalm/Config/ErrorLevelFileFilter.php b/src/Psalm/Config/ErrorLevelFileFilter.php index 7e9de88925e..de3ed732c19 100644 --- a/src/Psalm/Config/ErrorLevelFileFilter.php +++ b/src/Psalm/Config/ErrorLevelFileFilter.php @@ -1,5 +1,7 @@ */ - protected $directories = []; + protected array $directories = []; /** * @var array */ - protected $files = []; + protected array $files = []; /** * @var array */ - protected $fq_classlike_names = []; + protected array $fq_classlike_names = []; /** * @var array */ - protected $fq_classlike_patterns = []; + protected array $fq_classlike_patterns = []; /** * @var array */ - protected $method_ids = []; + protected array $method_ids = []; /** * @var array */ - protected $property_ids = []; + protected array $property_ids = []; /** * @var array */ - protected $class_constant_ids = []; + protected array $class_constant_ids = []; /** * @var array */ - protected $var_names = []; + protected array $var_names = []; /** * @var array */ - protected $files_lowercase = []; - - /** - * @var bool - */ - protected $inclusive; + protected array $files_lowercase = []; /** * @var array */ - protected $ignore_type_stats = []; + protected array $ignore_type_stats = []; /** * @var array */ - protected $declare_strict_types = []; + protected array $declare_strict_types = []; - public function __construct(bool $inclusive) + public function __construct(protected bool $inclusive) { - $this->inclusive = $inclusive; } /** @@ -114,8 +112,8 @@ public function __construct(bool $inclusive) public static function loadFromArray( array $config, string $base_dir, - bool $inclusive - ) { + bool $inclusive, + ): static { $allow_missing_files = ($config['allowMissingFiles'] ?? false) === true; $filter = new static($inclusive); @@ -135,9 +133,13 @@ public static function loadFromArray( $prospective_directory_path = $base_dir . DIRECTORY_SEPARATOR . $directory_path; } - if (strpos($prospective_directory_path, '*') !== false) { + if (str_contains($prospective_directory_path, '*')) { // Strip meaningless trailing recursive wildcard like "path/**/" or "path/**" - $prospective_directory_path = preg_replace('#(\/\*\*)+\/?$#', '/', $prospective_directory_path); + $prospective_directory_path = (string) preg_replace( + '#(\/\*\*)+\/?$#', + '/', + $prospective_directory_path, + ); // Split by /**/, allow duplicated wildcards like "path/**/**/path" and any leading dir separator. /** @var non-empty-list $path_parts */ $path_parts = preg_split('#(\/|\\\)(\*\*\/)+#', $prospective_directory_path); @@ -207,7 +209,7 @@ public static function loadFromArray( while ($iterator->valid()) { if ($iterator->isLink()) { - $linked_path = readlink($iterator->getPathname()); + $linked_path = (string) readlink($iterator->getPathname()); if (stripos($linked_path, $directory_path) !== 0) { if ($ignore_type_stats && $filter instanceof ProjectFileFilter) { @@ -254,7 +256,7 @@ public static function loadFromArray( $prospective_file_path = $base_dir . DIRECTORY_SEPARATOR . $file_path; } - if (strpos($prospective_file_path, '*') !== false) { + if (str_contains($prospective_file_path, '*')) { // Split by /**/, allow duplicated wildcards like "path/**/**/path" and any leading dir separator. /** @var non-empty-list $path_parts */ $path_parts = preg_split('#(\/|\\\)(\*\*\/)+#', $prospective_file_path); @@ -287,9 +289,13 @@ public static function loadFromArray( continue; } - $file_path = realpath($prospective_file_path); + $file_path = (string) realpath($prospective_file_path); + + if (!$file_path) { + if ($allow_missing_files) { + continue; + } - if (!$file_path && !$allow_missing_files) { throw new ConfigException( 'Could not resolve config path to ' . $prospective_file_path, ); @@ -304,7 +310,7 @@ public static function loadFromArray( foreach ($config['referencedClass'] as $referenced_class) { $class_name = strtolower((string) ($referenced_class['name'] ?? '')); - if (strpos($class_name, '*') !== false) { + if (str_contains($class_name, '*')) { $regex = '/' . str_replace('*', '.*', str_replace('\\', '\\\\', $class_name)) . '/i'; $filter->fq_classlike_patterns[] = $regex; } else { @@ -377,14 +383,11 @@ public static function loadFromArray( return $filter; } - /** - * @return static - */ public static function loadFromXMLElement( SimpleXMLElement $e, string $base_dir, - bool $inclusive - ) { + bool $inclusive, + ): static { $config = []; $config['allowMissingFiles'] = ((string) $e['allowMissingFiles']) === 'true'; @@ -493,6 +496,7 @@ private static function recursiveGlob(array $parts, bool $only_dir): array $first_dir = self::slashify($parts[0]); $paths = glob($first_dir . '*', GLOB_ONLYDIR | GLOB_NOSORT); + assert($paths !== false); $result = []; foreach ($paths as $path) { $parts[0] = $path; @@ -517,7 +521,7 @@ public function allows(string $file_name, bool $case_sensitive = false): bool if ($this->inclusive) { foreach ($this->directories as $include_dir) { if ($case_sensitive) { - if (strpos($file_name, $include_dir) === 0) { + if (str_starts_with($file_name, $include_dir)) { return true; } } else { @@ -543,7 +547,7 @@ public function allows(string $file_name, bool $case_sensitive = false): bool // exclusive foreach ($this->directories as $exclude_dir) { if ($case_sensitive) { - if (strpos($file_name, $exclude_dir) === 0) { + if (str_starts_with($file_name, $exclude_dir)) { return false; } } else { diff --git a/src/Psalm/Config/IssueHandler.php b/src/Psalm/Config/IssueHandler.php index 48791659e47..aba87f0232b 100644 --- a/src/Psalm/Config/IssueHandler.php +++ b/src/Psalm/Config/IssueHandler.php @@ -1,5 +1,7 @@ + * @var list */ private array $custom_levels = []; - public static function loadFromXMLElement(SimpleXMLElement $e, string $base_dir): IssueHandler + public static function loadFromXMLElement(SimpleXMLElement $e, string $base_dir): self { $handler = new self(); @@ -47,6 +50,12 @@ public static function loadFromXMLElement(SimpleXMLElement $e, string $base_dir) return $handler; } + /** @return list */ + public function getFilters(): array + { + return $this->custom_levels; + } + public function setCustomLevels(array $customLevels, string $base_dir): void { /** @var array $customLevel */ @@ -68,6 +77,7 @@ public function getReportingLevelForFile(string $file_path): string { foreach ($this->custom_levels as $custom_level) { if ($custom_level->allows($file_path)) { + $custom_level->suppressions++; return $custom_level->getErrorLevel(); } } @@ -79,6 +89,7 @@ public function getReportingLevelForClass(string $fq_classlike_name): ?string { foreach ($this->custom_levels as $custom_level) { if ($custom_level->allowsClass($fq_classlike_name)) { + $custom_level->suppressions++; return $custom_level->getErrorLevel(); } } @@ -90,6 +101,7 @@ public function getReportingLevelForMethod(string $method_id): ?string { foreach ($this->custom_levels as $custom_level) { if ($custom_level->allowsMethod(strtolower($method_id))) { + $custom_level->suppressions++; return $custom_level->getErrorLevel(); } } @@ -112,6 +124,7 @@ public function getReportingLevelForArgument(string $function_id): ?string { foreach ($this->custom_levels as $custom_level) { if ($custom_level->allowsMethod(strtolower($function_id))) { + $custom_level->suppressions++; return $custom_level->getErrorLevel(); } } @@ -123,6 +136,7 @@ public function getReportingLevelForProperty(string $property_id): ?string { foreach ($this->custom_levels as $custom_level) { if ($custom_level->allowsProperty($property_id)) { + $custom_level->suppressions++; return $custom_level->getErrorLevel(); } } @@ -134,6 +148,7 @@ public function getReportingLevelForClassConstant(string $constant_id): ?string { foreach ($this->custom_levels as $custom_level) { if ($custom_level->allowsClassConstant($constant_id)) { + $custom_level->suppressions++; return $custom_level->getErrorLevel(); } } @@ -145,6 +160,7 @@ public function getReportingLevelForVariable(string $var_name): ?string { foreach ($this->custom_levels as $custom_level) { if ($custom_level->allowsVariable($var_name)) { + $custom_level->suppressions++; return $custom_level->getErrorLevel(); } } @@ -157,10 +173,12 @@ public function getReportingLevelForVariable(string $var_name): ?string */ public static function getAllIssueTypes(): array { + $scan = scandir(dirname(__DIR__) . '/Issue', SCANDIR_SORT_NONE); + assert($scan !== false); return array_filter( array_map( static fn(string $file_name): string => substr($file_name, 0, -4), - scandir(dirname(__DIR__) . '/Issue', SCANDIR_SORT_NONE), + $scan, ), static fn(string $issue_name): bool => $issue_name !== '' && $issue_name !== 'MethodIssue' diff --git a/src/Psalm/Config/ProjectFileFilter.php b/src/Psalm/Config/ProjectFileFilter.php index 0395e227a05..2526413db04 100644 --- a/src/Psalm/Config/ProjectFileFilter.php +++ b/src/Psalm/Config/ProjectFileFilter.php @@ -1,26 +1,25 @@ ignoreFiles)) { @@ -60,7 +59,7 @@ public function reportTypeStats(string $file_name, bool $case_sensitive = false) { foreach ($this->ignore_type_stats as $exclude_dir => $_) { if ($case_sensitive) { - if (strpos($file_name, $exclude_dir) === 0) { + if (str_starts_with($file_name, $exclude_dir)) { return false; } } else { @@ -77,7 +76,7 @@ public function useStrictTypes(string $file_name, bool $case_sensitive = false): { foreach ($this->declare_strict_types as $exclude_dir => $_) { if ($case_sensitive) { - if (strpos($file_name, $exclude_dir) === 0) { + if (str_starts_with($file_name, $exclude_dir)) { return true; } } else { diff --git a/src/Psalm/Config/TaintAnalysisFileFilter.php b/src/Psalm/Config/TaintAnalysisFileFilter.php index 08b91a886d5..471b2122dc6 100644 --- a/src/Psalm/Config/TaintAnalysisFileFilter.php +++ b/src/Psalm/Config/TaintAnalysisFileFilter.php @@ -1,5 +1,7 @@ */ - public $vars_in_scope = []; + public array $vars_in_scope = []; /** * @var array */ - public $vars_possibly_in_scope = []; + public array $vars_possibly_in_scope = []; /** * Keeps track of how many times a var_in_scope has been referenced. May not be set for all $vars_in_scope. * * @var array> */ - public $referenced_counts = []; + public array $referenced_counts = []; /** * Maps references to referenced variables for the current scope. @@ -63,267 +66,207 @@ final class Context * * @var array */ - public $references_in_scope = []; + public array $references_in_scope = []; /** * Set of references to variables in another scope. These references will be marked as used if they are assigned to. * * @var array */ - public $references_to_external_scope = []; + public array $references_to_external_scope = []; /** * A set of globals that are referenced somewhere. * * @var array */ - public $referenced_globals = []; + public array $referenced_globals = []; /** * A set of references that might still be in scope from a scope likely to cause confusion. This applies * to references set inside a loop or if statement, since it's easy to forget about PHP's weird scope - * rules, and assinging to a reference will change the referenced variable rather than shadowing it. + * rules, and assigning to a reference will change the referenced variable rather than shadowing it. * * @var array */ - public $references_possibly_from_confusing_scope = []; + public array $references_possibly_from_confusing_scope = []; /** * Whether or not we're inside the conditional of an if/where etc. * * This changes whether or not the context is cloned - * - * @var bool */ - public $inside_conditional = false; + public bool $inside_conditional = false; /** * Whether or not we're inside an isset call * * Inside issets Psalm is more lenient about certain things - * - * @var bool */ - public $inside_isset = false; + public bool $inside_isset = false; /** * Whether or not we're inside an unset call, where * we don't care about possibly undefined variables - * - * @var bool */ - public $inside_unset = false; + public bool $inside_unset = false; /** - * Whether or not we're inside an class_exists call, where + * Whether or not we're inside a class_exists call, where * we don't care about possibly undefined classes - * - * @var bool */ - public $inside_class_exists = false; + public bool $inside_class_exists = false; /** * Whether or not we're inside a function/method call - * - * @var bool */ - public $inside_call = false; + public bool $inside_call = false; /** * Whether or not we're inside any other situation that treats a variable as used - * - * @var bool */ - public $inside_general_use = false; + public bool $inside_general_use = false; /** * Whether or not we're inside a return expression - * - * @var bool */ - public $inside_return = false; + public bool $inside_return = false; /** * Whether or not we're inside a throw - * - * @var bool */ - public $inside_throw = false; + public bool $inside_throw = false; /** * Whether or not we're inside an assignment - * - * @var bool */ - public $inside_assignment = false; + public bool $inside_assignment = false; /** * Whether or not we're inside a try block. - * - * @var bool - */ - public $inside_try = false; - - /** - * @var null|CodeLocation */ - public $include_location; + public bool $inside_try = false; - /** - * @var string|null - * The name of the current class. Null if outside a class. - */ - public $self; + public ?CodeLocation $include_location = null; - /** - * @var string|null - */ - public $parent; + public ?string $parent = null; - /** - * @var bool - */ - public $check_classes = true; + public bool $check_classes = true; - /** - * @var bool - */ - public $check_variables = true; + public bool $check_variables = true; - /** - * @var bool - */ - public $check_methods = true; + public bool $check_methods = true; - /** - * @var bool - */ - public $check_consts = true; + public bool $check_consts = true; - /** - * @var bool - */ - public $check_functions = true; + public bool $check_functions = true; /** * A list of classes checked with class_exists * * @var array */ - public $phantom_classes = []; + public array $phantom_classes = []; /** * A list of files checked with file_exists * * @var array */ - public $phantom_files = []; + public array $phantom_files = []; /** * A list of clauses in Conjunctive Normal Form * * @var list */ - public $clauses = []; + public array $clauses = []; /** * A list of hashed clauses that have already been factored in * * @var list */ - public $reconciled_expression_clauses = []; + public array $reconciled_expression_clauses = []; /** * Whether or not to do a deep analysis and collect mutations to this context - * - * @var bool */ - public $collect_mutations = false; + public bool $collect_mutations = false; /** * Whether or not to do a deep analysis and collect initializations from private or final methods - * - * @var bool */ - public $collect_initializations = false; + public bool $collect_initializations = false; /** * Whether or not to do a deep analysis and collect initializations from public non-final methods - * - * @var bool */ - public $collect_nonprivate_initializations = false; + public bool $collect_nonprivate_initializations = false; /** * Stored to prevent re-analysing methods when checking for initialised properties * - * @var array|null + * @var array */ - public $initialized_methods; + public array $initialized_methods = []; /** * @var array */ - public $constants = []; + public array $constants = []; /** * Whether or not to track exceptions - * - * @var bool */ - public $collect_exceptions = false; + public bool $collect_exceptions = false; /** * A list of variables that have been referenced in conditionals * * @var array */ - public $cond_referenced_var_ids = []; + public array $cond_referenced_var_ids = []; /** * A list of variables that have been passed by reference (where we know their type) * * @var array */ - public $byref_constraints = []; + public array $byref_constraints = []; /** * A list of vars that have been assigned to * * @var array */ - public $assigned_var_ids = []; + public array $assigned_var_ids = []; /** * A list of vars that have been may have been assigned to * * @var array */ - public $possibly_assigned_var_ids = []; + public array $possibly_assigned_var_ids = []; /** * A list of classes or interfaces that may have been thrown * * @var array> */ - public $possibly_thrown_exceptions = []; + public array $possibly_thrown_exceptions = []; - /** - * @var bool - */ - public $is_global = false; + public bool $is_global = false; /** * @var array */ - public $protected_var_ids = []; + public array $protected_var_ids = []; /** * If we've branched from the main scope, a byte offset for where that branch happened - * - * @var int|null */ - public $branch_point; + public ?int $branch_point = null; /** * What does break mean in this context? @@ -333,99 +276,64 @@ final class Context * * @var list<'loop'|'switch'> */ - public $break_types = []; + public array $break_types = []; - /** - * @var bool - */ - public $inside_loop = false; + public bool $inside_loop = false; - /** - * @var LoopScope|null - */ - public $loop_scope; + public ?LoopScope $loop_scope = null; - /** - * @var CaseScope|null - */ - public $case_scope; + public ?CaseScope $case_scope = null; - /** - * @var FinallyScope|null - */ - public $finally_scope; + public ?FinallyScope $finally_scope = null; - /** - * @var Context|null - */ - public $if_body_context; + public ?Context $if_body_context = null; - /** - * @var bool - */ - public $strict_types = false; + public bool $strict_types = false; - /** - * @var string|null - */ - public $calling_function_id; + public ?string $calling_function_id = null; /** * @var lowercase-string|null */ - public $calling_method_id; + public ?string $calling_method_id = null; - /** - * @var bool - */ - public $inside_negation = false; + public bool $inside_negation = false; - /** - * @var bool - */ - public $ignore_variable_property = false; + public bool $ignore_variable_property = false; - /** - * @var bool - */ - public $ignore_variable_method = false; + public bool $ignore_variable_method = false; - /** - * @var bool - */ - public $pure = false; + public bool $pure = false; /** * @var bool * Set by @psalm-immutable */ - public $mutation_free = false; + public bool $mutation_free = false; /** * @var bool * Set by @psalm-external-mutation-free */ - public $external_mutation_free = false; + public bool $external_mutation_free = false; - /** - * @var bool - */ - public $error_suppressing = false; + public bool $error_suppressing = false; - /** - * @var bool - */ - public $has_returned = false; + public bool $has_returned = false; /** * @var array */ - public $parent_remove_vars = []; + public array $parent_remove_vars = []; /** @internal */ - public function __construct(?string $self = null) - { - $this->self = $self; + public function __construct( + /** + * @var string|null + * The name of the current class. Null if outside a class. + */ + public ?string $self = null, + ) { } public function __destruct() @@ -446,7 +354,7 @@ public function update( Context $end_context, bool $has_leaving_statements, array $vars_to_update, - array &$updated_vars + array &$updated_vars, ): void { foreach ($start_context->vars_in_scope as $var_id => $old_type) { // this is only true if there was some sort of type negation @@ -495,7 +403,7 @@ public function update( */ public function updateReferencesPossiblyFromConfusingScope( Context $confusing_scope_context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): void { $references = $confusing_scope_context->references_in_scope + $confusing_scope_context->references_to_external_scope; @@ -670,7 +578,7 @@ public static function filterClauses( string $remove_var_id, array $clauses, ?Union $new_type = null, - ?StatementsAnalyzer $statements_analyzer = null + ?StatementsAnalyzer $statements_analyzer = null, ): array { $new_type_string = $new_type ? $new_type->getId() : ''; $clauses_to_keep = []; @@ -736,7 +644,7 @@ public static function filterClauses( public function removeVarFromConflictingClauses( string $remove_var_id, ?Union $new_type = null, - ?StatementsAnalyzer $statements_analyzer = null + ?StatementsAnalyzer $statements_analyzer = null, ): void { $this->clauses = self::filterClauses($remove_var_id, $this->clauses, $new_type, $statements_analyzer); $this->parent_remove_vars[$remove_var_id] = true; @@ -750,7 +658,7 @@ public function removeDescendents( string $remove_var_id, Union $existing_type, ?Union $new_type = null, - ?StatementsAnalyzer $statements_analyzer = null + ?StatementsAnalyzer $statements_analyzer = null, ): void { $this->removeVarFromConflictingClauses( $remove_var_id, @@ -787,7 +695,7 @@ public function removeMutableObjectVars(bool $methods_only = false): void foreach ($this->vars_in_scope as $var_id => $type) { if ($type->has_mutations - && (strpos($var_id, '->') !== false || strpos($var_id, '::') !== false) + && (str_contains($var_id, '->') || str_contains($var_id, '::')) && (!$methods_only || strpos($var_id, '()')) ) { $vars_to_remove[] = $var_id; @@ -808,7 +716,7 @@ public function removeMutableObjectVars(bool $methods_only = false): void $abandon_clause = false; foreach (array_keys($clause->possibilities) as $key) { - if ((strpos($key, '->') !== false || strpos($key, '::') !== false) + if ((str_contains($key, '->') || str_contains($key, '::')) && (!$methods_only || strpos($key, '()')) ) { $abandon_clause = true; @@ -844,7 +752,7 @@ public function hasVariable(string $var_name): bool return false; } - $stripped_var = preg_replace('/(->|\[).*$/', '', $var_name, 1); + $stripped_var = (string) preg_replace('/(->|\[).*$/', '', $var_name, 1); if ($stripped_var !== '$this' || $var_name !== $stripped_var) { $this->cond_referenced_var_ids[$var_name] = true; @@ -929,7 +837,7 @@ public function isSuppressingExceptions(StatementsAnalyzer $statements_analyzer) public function mergeFunctionExceptions( FunctionLikeStorage $function_storage, - CodeLocation $codelocation + CodeLocation $codelocation, ): void { $hash = $codelocation->getHash(); foreach ($function_storage->throws as $possibly_thrown_exception => $_) { diff --git a/src/Psalm/DocComment.php b/src/Psalm/DocComment.php index 79547e777ca..5310e8b983f 100644 --- a/src/Psalm/DocComment.php +++ b/src/Psalm/DocComment.php @@ -1,5 +1,7 @@ tags as $special_key => $_) { - if (strpos($special_key, 'psalm-') === 0) { + if (str_starts_with($special_key, 'psalm-')) { $special_key = substr($special_key, 6); if (!in_array( diff --git a/src/Psalm/ErrorBaseline.php b/src/Psalm/ErrorBaseline.php index 783ef4a8c84..0036c0b6105 100644 --- a/src/Psalm/ErrorBaseline.php +++ b/src/Psalm/ErrorBaseline.php @@ -1,5 +1,7 @@ textContent)); } - - // TODO: Remove in Psalm 6 - $occurrencesAttr = $issue->getAttribute('occurrences'); - if ($occurrencesAttr !== '') { - $files[$fileName][$issueType]['o'] = (int) $occurrencesAttr; - } } } @@ -140,7 +136,7 @@ public static function update( FileProvider $fileProvider, string $baselineFile, array $issues, - bool $include_php_versions + bool $include_php_versions, ): array { $existingIssues = self::read($fileProvider, $baselineFile); $newIssues = self::countIssueTypesByFile($issues); @@ -235,7 +231,7 @@ private static function writeToFile( FileProvider $fileProvider, string $baselineFile, array $groupedIssues, - bool $include_php_versions + bool $include_php_versions, ): void { $baselineDoc = new DOMDocument('1.0', 'UTF-8'); $filesNode = $baselineDoc->createElement('files'); @@ -249,7 +245,7 @@ private static function writeToFile( $filesNode->setAttribute('php-version', implode(';' . "\n\t", [...[ ('php:' . PHP_VERSION), ], ...array_map( - static fn(string $extension): string => $extension . ':' . phpversion($extension), + static fn(string $extension): string => $extension . ':' . (string) phpversion($extension), $extensions, )])); } diff --git a/src/Psalm/Exception/CircularReferenceException.php b/src/Psalm/Exception/CircularReferenceException.php index 178991f6e9d..51bdf98b31e 100644 --- a/src/Psalm/Exception/CircularReferenceException.php +++ b/src/Psalm/Exception/CircularReferenceException.php @@ -1,5 +1,7 @@ class_name = $class_name; - $this->const_name = $const_name; } } diff --git a/src/Psalm/Exception/UnsupportedIssueToFixException.php b/src/Psalm/Exception/UnsupportedIssueToFixException.php index c1392d5ea15..bcd568e5d0f 100644 --- a/src/Psalm/Exception/UnsupportedIssueToFixException.php +++ b/src/Psalm/Exception/UnsupportedIssueToFixException.php @@ -1,5 +1,7 @@ path = $path; - $this->config = $config; - $this->codebase = $codebase; } public function __invoke(RegistrationInterface $registration, ?SimpleXMLElement $config = null): void @@ -63,6 +63,8 @@ private function getPluginClassForPath(string $path): string $declared_classes = ClassLikeAnalyzer::getClassesForFile($codebase, $path); + assert(count($declared_classes) > 0, 'FileBasedPlugin contains a class'); + return reset($declared_classes); } } diff --git a/src/Psalm/FileManipulation.php b/src/Psalm/FileManipulation.php index afa3ea93c10..f475b0b6855 100644 --- a/src/Psalm/FileManipulation.php +++ b/src/Psalm/FileManipulation.php @@ -1,5 +1,7 @@ start = $start; - $this->end = $end; - $this->insertion_text = $insertion_text; - $this->preserve_indentation = $preserve_indentation; - $this->remove_trailing_newline = $remove_trailing_newline; } public function getKey(): string diff --git a/src/Psalm/FileSource.php b/src/Psalm/FileSource.php index fd672862365..0bbd90e89da 100644 --- a/src/Psalm/FileSource.php +++ b/src/Psalm/FileSource.php @@ -1,5 +1,7 @@ type instanceof TList - || $assertion->type instanceof TArray + if ($assertion->type instanceof TArray || $assertion->type instanceof TKeyedArray) { $has_list_or_array = true; // list/array are collapsed, therefore there can only be 1 and we can abort @@ -434,8 +434,7 @@ public static function getTruthsFromFormula( continue; } - if ($assertion->type instanceof TList - || $assertion->type instanceof TArray + if ($assertion->type instanceof TArray || $assertion->type instanceof TKeyedArray) { unset($truths[$var][$key][$index]); } @@ -588,7 +587,7 @@ public static function groupImpossibilities(array $clauses): array public static function combineOredClauses( array $left_clauses, array $right_clauses, - int $conditional_object_id + int $conditional_object_id, ): array { if (count($left_clauses) > 60_000 || count($right_clauses) > 60_000) { return []; diff --git a/src/Psalm/Internal/Algebra/FormulaGenerator.php b/src/Psalm/Internal/Algebra/FormulaGenerator.php index b6810291e5d..e6e42b7b4f9 100644 --- a/src/Psalm/Internal/Algebra/FormulaGenerator.php +++ b/src/Psalm/Internal/Algebra/FormulaGenerator.php @@ -1,5 +1,7 @@ getCodebase(); $appearing_non_repeatable_attributes = []; @@ -138,7 +140,7 @@ private static function analyzeAttributeConstruction( string $fq_attribute_name, Attribute $attribute, array $suppressed_issues, - ?ClassLikeStorage $classlike_storage = null + ?ClassLikeStorage $classlike_storage = null, ): void { $attribute_name_location = new CodeLocation($source, $attribute->name); @@ -244,7 +246,7 @@ private static function getAttributeClassFlags( string $fq_attribute_name, CodeLocation $attribute_name_location, ?ClassLikeStorage $attribute_class_storage, - array $suppressed_issues + array $suppressed_issues, ): int { if (strtolower($fq_attribute_name) === "attribute") { // We override this here because we still want to analyze attributes @@ -262,7 +264,7 @@ private static function getAttributeClassFlags( return self::TARGET_ALL; // Defaults to TARGET_ALL } - $first_arg = reset($attribute_attribute->args); + $first_arg = $attribute_attribute->args[array_key_first($attribute_attribute->args)]; $first_arg_type = $first_arg->type; @@ -316,7 +318,7 @@ private static function iterateAttributeNodes(iterable $attribute_groups): Gener public static function analyzeGetAttributes( StatementsAnalyzer $statements_analyzer, string $method_id, - array $args + array $args, ): void { if (count($args) !== 1) { // We skip this analysis if $flags is specified on getAttributes, since the only option diff --git a/src/Psalm/Internal/Analyzer/CanAlias.php b/src/Psalm/Internal/Analyzer/CanAlias.php index 3be3d60d7bd..32bddf3a17b 100644 --- a/src/Psalm/Internal/Analyzer/CanAlias.php +++ b/src/Psalm/Internal/Analyzer/CanAlias.php @@ -1,5 +1,7 @@ getLine() @@ -160,7 +162,7 @@ public static function getAnonymousClassName( public function analyze( ?Context $class_context = null, - ?Context $global_context = null + ?Context $global_context = null, ): void { $class = $this->class; @@ -271,8 +273,6 @@ public function analyze( IssueBuffer::maybeAdd($docblock_issue); } - $classlike_storage_provider = $codebase->classlike_storage_provider; - $parent_fq_class_name = $this->parent_fq_class_name; if ($class instanceof PhpParser\Node\Stmt\Class_ && $class->extends && $parent_fq_class_name) { @@ -595,7 +595,7 @@ public function analyze( try { $trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name); - } catch (Exception $e) { + } catch (Exception) { continue; } @@ -639,43 +639,7 @@ public function analyze( } $pseudo_methods = $storage->pseudo_methods + $storage->pseudo_static_methods; - - foreach ($pseudo_methods as $pseudo_method_name => $pseudo_method_storage) { - $pseudo_method_id = new MethodIdentifier( - $this->fq_class_name, - $pseudo_method_name, - ); - - $overridden_method_ids = $codebase->methods->getOverriddenMethodIds($pseudo_method_id); - - if ($overridden_method_ids - && $pseudo_method_name !== '__construct' - && $pseudo_method_storage->location - ) { - foreach ($overridden_method_ids as $overridden_method_id) { - $parent_method_storage = $codebase->methods->getStorage($overridden_method_id); - - $overridden_fq_class_name = $overridden_method_id->fq_class_name; - - $parent_storage = $classlike_storage_provider->get($overridden_fq_class_name); - - MethodComparator::compare( - $codebase, - null, - $storage, - $parent_storage, - $pseudo_method_storage, - $parent_method_storage, - $this->fq_class_name, - $pseudo_method_storage->visibility ?: 0, - $storage->location ?: $pseudo_method_storage->location, - $storage->suppressed_issues, - true, - false, - ); - } - } - } + MethodComparator::comparePseudoMethods($pseudo_methods, $this->fq_class_name, $codebase, $storage); $event = new AfterClassLikeAnalysisEvent( $class, @@ -703,7 +667,7 @@ public static function addContextProperties( Context $class_context, string $fq_class_name, ?string $parent_fq_class_name, - array $stmts = [] + array $stmts = [], ): void { $codebase = $statements_source->getCodebase(); @@ -969,7 +933,7 @@ public static function addContextProperties( try { $docBlock = DocComment::parsePreservingLength($docComment); $suppressed = $docBlock->tags['psalm-suppress'] ?? []; - } catch (DocblockParseException $e) { + } catch (DocblockParseException) { // do nothing to keep original behavior } } @@ -1043,7 +1007,7 @@ private function checkPropertyInitialization( ClassLikeStorage $storage, Context $class_context, ?Context $global_context = null, - ?MethodAnalyzer $constructor_analyzer = null + ?MethodAnalyzer $constructor_analyzer = null, ): void { if (!$config->reportIssueInFile('PropertyNotSetInConstructor', $this->getFilePath())) { return; @@ -1264,7 +1228,7 @@ static function (FunctionLikeParameter $param): PhpParser\Node\Arg { $fake_stmt = new VirtualClassMethod( new VirtualIdentifier('__construct'), [ - 'flags' => PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC, + 'flags' => PhpParser\Modifiers::PUBLIC, 'params' => $fake_constructor_params, 'stmts' => $fake_constructor_stmts, ], @@ -1416,7 +1380,7 @@ private function analyzeTraitUse( Context $class_context, ?Context $global_context = null, ?MethodAnalyzer &$constructor_analyzer = null, - ?TraitAnalyzer $previous_trait_analyzer = null + ?TraitAnalyzer $previous_trait_analyzer = null, ): ?bool { $codebase = $this->getCodebase(); @@ -1568,7 +1532,7 @@ private function analyzeTraitUse( private function analyzeProperty( SourceAnalyzer $source, PhpParser\Node\Stmt\Property $stmt, - Context $context + Context $context, ): void { $fq_class_name = $source->getFQCLN(); $property_name = $stmt->props[0]->name->name; @@ -1670,7 +1634,7 @@ private static function addOrUpdatePropertyType( PhpParser\Node\Stmt\Property $property, Union $inferred_type, StatementsSource $source, - bool $docblock_only = false + bool $docblock_only = false, ): void { $manipulator = PropertyDocblockManipulator::getForProperty( $project_analyzer, @@ -1718,7 +1682,7 @@ private function analyzeClassMethod( SourceAnalyzer $source, Context $class_context, ?Context $global_context = null, - bool $is_fake = false + bool $is_fake = false, ): ?MethodAnalyzer { $config = Config::getInstance(); @@ -1893,7 +1857,7 @@ private function analyzeClassMethod( private static function getThisObjectType( ClassLikeStorage $class_storage, - string $original_fq_classlike_name + string $original_fq_classlike_name, ): TNamedObject { if ($class_storage->template_types) { $template_params = []; @@ -1929,7 +1893,7 @@ public static function analyzeClassMethodReturnType( string $fq_classlike_name, MethodIdentifier $analyzed_method_id, MethodIdentifier $actual_method_id, - bool $did_explicitly_return + bool $did_explicitly_return, ): void { $secondary_return_type_location = null; @@ -2057,7 +2021,7 @@ private function checkImplementedInterfaces( PhpParser\Node\Stmt $class, Codebase $codebase, string $fq_class_name, - ClassLikeStorage $storage + ClassLikeStorage $storage, ): bool { $classlike_storage_provider = $codebase->classlike_storage_provider; @@ -2115,7 +2079,7 @@ private function checkImplementedInterfaces( try { $interface_storage = $classlike_storage_provider->get($fq_interface_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return false; } @@ -2149,7 +2113,7 @@ private function checkImplementedInterfaces( foreach ($storage->class_implements as $fq_interface_name_lc => $fq_interface_name) { try { $interface_storage = $classlike_storage_provider->get($fq_interface_name_lc); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return false; } @@ -2360,7 +2324,7 @@ private function checkParentClass( string $parent_fq_class_name, ClassLikeStorage $storage, Codebase $codebase, - ?Context $class_context + ?Context $class_context, ): void { $classlike_storage_provider = $codebase->classlike_storage_provider; @@ -2512,7 +2476,7 @@ private function checkParentClass( $code_location, $storage->template_type_extends_count[$parent_fq_class_name] ?? 0, ); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // do nothing } } @@ -2541,8 +2505,8 @@ private function checkEnum(): void ), ); } elseif ($case_value !== null) { - if ((is_int($case_value) && $storage->enum_type === 'string') - || (is_string($case_value) && $storage->enum_type === 'int') + if (($case_value instanceof TLiteralInt && $storage->enum_type === 'string') + || ($case_value instanceof TLiteralString && $storage->enum_type === 'int') ) { IssueBuffer::maybeAdd( new InvalidEnumCaseValue( @@ -2555,7 +2519,7 @@ private function checkEnum(): void } if ($case_value !== null) { - if (in_array($case_value, $seen_values, true)) { + if (in_array($case_value->value, $seen_values, true)) { IssueBuffer::maybeAdd( new DuplicateEnumCaseValue( 'Enum case values should be unique', @@ -2564,7 +2528,7 @@ private function checkEnum(): void ), ); } else { - $seen_values[] = $case_value; + $seen_values[] = $case_value->value; } } } diff --git a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php index bc20c08f8ae..47e716f2225 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeAnalyzer.php @@ -1,5 +1,7 @@ true, ]; - protected PhpParser\Node\Stmt\ClassLike $class; - public FileAnalyzer $file_analyzer; - protected string $fq_class_name; - /** * The parent class */ @@ -93,12 +91,13 @@ abstract class ClassLikeAnalyzer extends SourceAnalyzer protected ClassLikeStorage $storage; - public function __construct(PhpParser\Node\Stmt\ClassLike $class, SourceAnalyzer $source, string $fq_class_name) - { - $this->class = $class; + public function __construct( + protected PhpParser\Node\Stmt\ClassLike $class, + SourceAnalyzer $source, + protected string $fq_class_name, + ) { $this->source = $source; $this->file_analyzer = $source->getFileAnalyzer(); - $this->fq_class_name = $fq_class_name; $codebase = $source->getCodebase(); $this->storage = $codebase->classlike_storage_provider->get($fq_class_name); } @@ -111,7 +110,7 @@ public function __destruct() public function getMethodMutations( string $method_name, - Context $context + Context $context, ): void { $project_analyzer = $this->getFileAnalyzer()->project_analyzer; $codebase = $project_analyzer->getCodebase(); @@ -204,7 +203,7 @@ public static function checkFullyQualifiedClassLikeName( ?string $calling_method_id, array $suppressed_issues, ?ClassLikeNameOptions $options = null, - bool $check_classes = true + bool $check_classes = true, ): ?bool { if ($options === null) { $options = new ClassLikeNameOptions(); @@ -226,7 +225,7 @@ public static function checkFullyQualifiedClassLikeName( return null; } - $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name, 1); + $fq_class_name = (string) preg_replace('/^\\\/', '', $fq_class_name, 1); if (in_array($fq_class_name, ['callable', 'iterable', 'self', 'static', 'parent'], true)) { return true; @@ -395,7 +394,7 @@ public static function checkFullyQualifiedClassLikeName( */ public static function getFQCLNFromNameObject( PhpParser\Node\Name $class_name, - Aliases $aliases + Aliases $aliases, ): string { /** @var string|null */ $resolved_name = $class_name->getAttribute('resolvedName'); @@ -479,10 +478,8 @@ public function isStatic(): bool /** * Gets the Psalm type from a particular value - * - * @param mixed $value */ - public static function getTypeFromValue($value): Union + public static function getTypeFromValue(mixed $value): Union { switch (gettype($value)) { case 'boolean': @@ -521,7 +518,7 @@ public static function checkPropertyVisibility( SourceAnalyzer $source, CodeLocation $code_location, array $suppressed_issues, - bool $emit_issues = true + bool $emit_issues = true, ): ?bool { [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -634,7 +631,7 @@ protected function checkTemplateParams( ClassLikeStorage $storage, ClassLikeStorage $parent_storage, CodeLocation $code_location, - int $given_param_count + int $given_param_count, ): void { $expected_param_count = $parent_storage->template_types === null ? 0 @@ -806,7 +803,7 @@ public static function getClassesForFile(Codebase $codebase, string $file_path): { try { return $codebase->file_storage_provider->get($file_path)->classlikes_in_file; - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return []; } } diff --git a/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php b/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php index 9ae064cab5d..64e9db31885 100644 --- a/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php +++ b/src/Psalm/Internal/Analyzer/ClassLikeNameOptions.php @@ -1,5 +1,7 @@ inferred = $inferred; - $this->allow_trait = $allow_trait; - $this->allow_interface = $allow_interface; - $this->allow_enum = $allow_enum; - $this->from_docblock = $from_docblock; - $this->from_attribute = $from_attribute; } } diff --git a/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php b/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php index 8a69cf2fd10..0149e800c92 100644 --- a/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ClosureAnalyzer.php @@ -1,5 +1,7 @@ vars_in_scope as $var => $type) { - if (strpos($var, '$this->') === 0) { + if (str_starts_with($var, '$this->')) { $use_context->vars_in_scope[$var] = $type; } } @@ -119,7 +123,7 @@ public static function analyzeExpression( } foreach ($context->vars_possibly_in_scope as $var => $_) { - if (strpos($var, '$this->') === 0) { + if (str_starts_with($var, '$this->')) { $use_context->vars_possibly_in_scope[$var] = true; } } @@ -230,7 +234,7 @@ public static function analyzeExpression( private static function analyzeClosureUses( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\Closure $stmt, - Context $context + Context $context, ): ?bool { $param_names = []; diff --git a/src/Psalm/Internal/Analyzer/CommentAnalyzer.php b/src/Psalm/Internal/Analyzer/CommentAnalyzer.php index ec06f1878fe..572df0f5078 100644 --- a/src/Psalm/Internal/Analyzer/CommentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/CommentAnalyzer.php @@ -1,5 +1,7 @@ deprecated = isset($parsed_docblock->tags['deprecated']); $var_comment->internal = isset($parsed_docblock->tags['internal']); @@ -258,10 +260,11 @@ private static function decorateVarDocblockComment( */ public static function sanitizeDocblockType(string $docblock_type): string { - $docblock_type = preg_replace('@^[ \t]*\*@m', '', $docblock_type); - $docblock_type = preg_replace('/,\n\s+}/', '}', $docblock_type); + $docblock_type = (string) preg_replace('@^[ \t]*\*@m', '', $docblock_type); + $docblock_type = (string) preg_replace('/,[\n\s]+}/', '}', $docblock_type); + $docblock_type = (string) preg_replace('/[ \t]+/', ' ', $docblock_type); - return str_replace("\n", '', $docblock_type); + return trim(str_replace("\n", '', $docblock_type)); } /** @@ -396,7 +399,7 @@ public static function splitDocLine(string $return_block): array continue; } - $remaining = trim(preg_replace('@^[ \t]*\* *@m', ' ', substr($return_block, $i + 1))); + $remaining = trim((string) preg_replace('@^[ \t]*\* *@m', ' ', substr($return_block, $i + 1))); if ($remaining) { return [rtrim($type), ...preg_split('/\s+/', $remaining) ?: []]; @@ -417,7 +420,7 @@ public static function splitDocLine(string $return_block): array public static function getVarComments( PhpParser\Comment\Doc $doc_comment, StatementsAnalyzer $statements_analyzer, - PhpParser\Node\Expr\Variable $var + PhpParser\Node\Expr\Variable $var, ): array { $codebase = $statements_analyzer->getCodebase(); $parsed_docblock = $statements_analyzer->getParsedDocblock(); @@ -429,6 +432,10 @@ public static function getVarComments( $var_comments = []; try { + $file_path = $statements_analyzer->getRootFilePath(); + $file_storage_provider = $codebase->file_storage_provider; + $file_storage = $file_storage_provider->get($file_path); + $var_comments = $codebase->config->disable_var_parsing ? [] : self::arrayToDocblocks( @@ -437,6 +444,7 @@ public static function getVarComments( $statements_analyzer->getSource(), $statements_analyzer->getSource()->getAliases(), $statements_analyzer->getSource()->getTemplateTypeMap(), + $file_storage->type_aliases, ); } catch (IncorrectDocblockException $e) { IssueBuffer::maybeAdd( @@ -464,7 +472,7 @@ public static function populateVarTypesFromDocblock( array $var_comments, PhpParser\Node\Expr\Variable $var, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): ?Union { if (!is_string($var->name)) { return null; diff --git a/src/Psalm/Internal/Analyzer/DataFlowNodeData.php b/src/Psalm/Internal/Analyzer/DataFlowNodeData.php index df96e6fe9a2..c5711d701ad 100644 --- a/src/Psalm/Internal/Analyzer/DataFlowNodeData.php +++ b/src/Psalm/Internal/Analyzer/DataFlowNodeData.php @@ -1,5 +1,7 @@ label = $label; - $this->line_from = $line_from; - $this->line_to = $line_to; - $this->file_name = $file_name; - $this->file_path = $file_path; - $this->snippet = $snippet; - $this->from = $from; - $this->to = $to; - $this->snippet_from = $snippet_from; - $this->column_from = $column_from; - $this->column_to = $column_to; } } diff --git a/src/Psalm/Internal/Analyzer/FileAnalyzer.php b/src/Psalm/Internal/Analyzer/FileAnalyzer.php index a6dc95618b8..8c17c0e7281 100644 --- a/src/Psalm/Internal/Analyzer/FileAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FileAnalyzer.php @@ -1,5 +1,7 @@ @@ -93,8 +91,6 @@ class FileAnalyzer extends SourceAnalyzer public ?Context $context = null; - public ProjectAnalyzer $project_analyzer; - public Codebase $codebase; private int $first_statement_offset = -1; @@ -103,18 +99,18 @@ class FileAnalyzer extends SourceAnalyzer private ?Union $return_type = null; - public function __construct(ProjectAnalyzer $project_analyzer, string $file_path, string $file_name) - { + public function __construct( + public ProjectAnalyzer $project_analyzer, + protected string $file_path, + protected string $file_name, + ) { $this->source = $this; - $this->file_path = $file_path; - $this->file_name = $file_name; - $this->project_analyzer = $project_analyzer; $this->codebase = $project_analyzer->getCodebase(); } public function analyze( ?Context $file_context = null, - ?Context $global_context = null + ?Context $global_context = null, ): void { $codebase = $this->project_analyzer->getCodebase(); @@ -146,7 +142,7 @@ public function analyze( try { $stmts = $codebase->getStatementsForFile($this->file_path); - } catch (PhpParser\Error $e) { + } catch (PhpParser\Error) { return; } @@ -362,7 +358,7 @@ public function addNamespacedInterfaceAnalyzer(string $fq_class_name, InterfaceA public function getMethodMutations( MethodIdentifier $method_id, Context $this_context, - bool $from_project_analyzer = false + bool $from_project_analyzer = false, ): void { $fq_class_name = $method_id->fq_class_name; $method_name = $method_id->method_name; @@ -392,13 +388,13 @@ public function getMethodMutations( $call_context->calling_method_id = $this_context->calling_method_id; foreach ($this_context->vars_possibly_in_scope as $var => $_) { - if (strpos($var, '$this->') === 0) { + if (str_starts_with($var, '$this->')) { $call_context->vars_possibly_in_scope[$var] = true; } } foreach ($this_context->vars_in_scope as $var => $type) { - if (strpos($var, '$this->') === 0) { + if (str_starts_with($var, '$this->')) { $call_context->vars_in_scope[$var] = $type; } } diff --git a/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php index 8aec6258a3c..3c8a293b0be 100644 --- a/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionAnalyzer.php @@ -1,10 +1,13 @@ getCodebase(); @@ -54,7 +58,7 @@ public function getFunctionId(): string public static function analyzeStatement( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Stmt\Function_ $stmt, - Context $context + Context $context, ): void { foreach ($stmt->stmts as $function_stmt) { if ($function_stmt instanceof PhpParser\Node\Stmt\Global_) { diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php index 763bd3e2f01..27f16d8499e 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeAnalyzer.php @@ -1,5 +1,7 @@ getSuppressedIssues(); $codebase = $source->getCodebase(); @@ -125,7 +126,7 @@ public static function verifyReturnType( $is_to_string = $function instanceof ClassMethod && strtolower($function->name->name) === '__tostring'; if ($function instanceof ClassMethod - && strpos($function->name->name, '__') === 0 + && str_starts_with($function->name->name, '__') && !$is_to_string && !$return_type ) { @@ -505,17 +506,6 @@ public static function verifyReturnType( } if ($inferred_return_type->hasMixed()) { - if (IssueBuffer::accepts( - new MixedInferredReturnType( - 'Could not verify return type \'' . $declared_return_type . '\' for ' . - $cased_method_id, - $return_type_location, - ), - $suppressed_issues, - )) { - return false; - } - return null; } @@ -822,7 +812,7 @@ public static function checkReturnType( ProjectAnalyzer $project_analyzer, FunctionLikeAnalyzer $function_like_analyzer, FunctionLikeStorage $storage, - Context $context + Context $context, ): ?bool { $codebase = $project_analyzer->getCodebase(); @@ -1018,7 +1008,7 @@ private static function addOrUpdateReturnType( Union $inferred_return_type, StatementsSource $source, bool $docblock_only = false, - ?FunctionLikeStorage $function_like_storage = null + ?FunctionLikeStorage $function_like_storage = null, ): void { $manipulator = FunctionDocblockManipulator::getForFunction( $project_analyzer, diff --git a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php index 2d99b3435af..9d6a0bf62d2 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php +++ b/src/Psalm/Internal/Analyzer/FunctionLike/ReturnTypeCollector.php @@ -1,5 +1,7 @@ expr, $nodes)); } elseif ($stmt->expr instanceof PhpParser\Node\Scalar\String_) { $return_types[] = Type::getString(); - } elseif ($stmt->expr instanceof PhpParser\Node\Scalar\LNumber) { + } elseif ($stmt->expr instanceof PhpParser\Node\Scalar\Int_) { $return_types[] = Type::getInt(); } elseif ($stmt->expr instanceof PhpParser\Node\Expr\ConstFetch) { if ((string)$stmt->expr->name === 'true') { @@ -76,14 +77,9 @@ public static function getReturnTypes( break; } - if ($stmt instanceof PhpParser\Node\Stmt\Throw_) { - $return_types[] = Type::getNever(); - - break; - } - if ($stmt instanceof PhpParser\Node\Stmt\Expression) { - if ($stmt->expr instanceof PhpParser\Node\Expr\Exit_) { + if ($stmt->expr instanceof PhpParser\Node\Expr\Exit_ + || $stmt->expr instanceof PhpParser\Node\Expr\Throw_) { $return_types[] = Type::getNever(); break; @@ -257,7 +253,7 @@ public static function getReturnTypes( private static function processYieldTypes( Codebase $codebase, array $return_types, - array $yield_types + array $yield_types, ): array { $key_type = null; $value_type = null; @@ -265,10 +261,6 @@ private static function processYieldTypes( $yield_type = Type::combineUnionTypeArray($yield_types, null); foreach ($yield_type->getAtomicTypes() as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } - if ($type instanceof TKeyedArray) { $type = $type->getGenericArrayType(); } @@ -310,7 +302,7 @@ private static function processYieldTypes( */ private static function getYieldTypeFromExpression( PhpParser\Node\Expr $stmt, - NodeDataProvider $nodes + NodeDataProvider $nodes, ): array { $collector = new YieldTypeCollector($nodes); $traverser = new NodeTraverser(); diff --git a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php index b6d941e98ea..502b9504ec7 100644 --- a/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/FunctionLikeAnalyzer.php @@ -1,5 +1,7 @@ function = $function; + public function __construct( + protected Closure|Function_|ClassMethod|ArrowFunction $function, + SourceAnalyzer $source, + protected FunctionLikeStorage $storage, + ) { $this->source = $source; $this->suppressed_issues = $source->getSuppressedIssues(); $this->codebase = $source->getCodebase(); - $this->storage = $storage; } /** @@ -166,7 +163,7 @@ public function analyze( NodeDataProvider $type_provider, ?Context $global_context = null, bool $add_mutations = false, - array &$byref_vars = [] + array &$byref_vars = [], ): ?bool { $storage = $this->storage; @@ -842,20 +839,20 @@ public function analyze( } if ($this->return_vars_possibly_in_scope !== null) { - $context->vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $this->return_vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$this->return_vars_possibly_in_scope, + ]; } foreach ($context->vars_in_scope as $var => $_) { - if (strpos($var, '$this->') !== 0 && $var !== '$this') { + if (!str_starts_with($var, '$this->') && $var !== '$this') { $context->removePossibleReference($var); } } foreach ($context->vars_possibly_in_scope as $var => $_) { - if (strpos($var, '$this->') !== 0 && $var !== '$this') { + if (!str_starts_with($var, '$this->') && $var !== '$this') { unset($context->vars_possibly_in_scope[$var]); } } @@ -912,7 +909,7 @@ private function checkParamReferences( StatementsAnalyzer $statements_analyzer, FunctionLikeStorage $storage, ?ClassLikeStorage $class_storage, - Context $context + Context $context, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -1013,7 +1010,7 @@ private function processParams( array $params, array $param_stmts, Context $context, - bool $has_template_types + bool $has_template_types, ): bool { $check_stmts = true; $codebase = $statements_analyzer->getCodebase(); @@ -1369,7 +1366,7 @@ private function alterParams( Codebase $codebase, FunctionLikeStorage $storage, array $params, - Context $context + Context $context, ): void { foreach ($this->function->params as $param) { $param_name_node = null; @@ -1497,7 +1494,7 @@ public function verifyReturnType( ?string $fq_class_name = null, ?CodeLocation $return_type_location = null, bool $did_explicitly_return = false, - bool $closure_inside_call = false + bool $closure_inside_call = false, ): void { ReturnTypeAnalyzer::verifyReturnType( $this->function, @@ -1519,7 +1516,7 @@ public function addOrUpdateParamType( ProjectAnalyzer $project_analyzer, string $param_name, Union $inferred_return_type, - bool $docblock_only = false + bool $docblock_only = false, ): void { $manipulator = FunctionDocblockManipulator::getForFunction( $project_analyzer, @@ -1583,10 +1580,10 @@ public function addReturnTypes(Context $context): void } if ($this->return_vars_possibly_in_scope !== null) { - $this->return_vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $this->return_vars_possibly_in_scope, - ); + $this->return_vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$this->return_vars_possibly_in_scope, + ]; } else { $this->return_vars_possibly_in_scope = $context->vars_possibly_in_scope; } @@ -1596,7 +1593,7 @@ public function examineParamTypes( StatementsAnalyzer $statements_analyzer, Context $context, Codebase $codebase, - ?PhpParser\Node $stmt = null + ?PhpParser\Node $stmt = null, ): void { $storage = $this->getFunctionLikeStorage($statements_analyzer); @@ -1673,7 +1670,7 @@ public function getFunctionLikeStorage(?StatementsAnalyzer $statements_analyzer try { return $codebase_methods->getStorage($method_id); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { $declaring_method_id = $codebase_methods->getDeclaringMethodId($method_id); if ($declaring_method_id === null) { @@ -1852,7 +1849,7 @@ private function getFunctionInformation( Codebase $codebase, NodeDataProvider $type_provider, FunctionLikeStorage $storage, - bool $add_mutations + bool $add_mutations, ): ?array { $classlike_storage_provider = $codebase->classlike_storage_provider; $real_method_id = null; @@ -2134,7 +2131,7 @@ private function getFunctionInformation( private function detectUnusedParameters( StatementsAnalyzer $statements_analyzer, FunctionLikeStorage $storage, - Context $context + Context $context, ): array { $codebase = $statements_analyzer->getCodebase(); @@ -2241,6 +2238,6 @@ private function detectPreviousUnusedArgumentPosition(FunctionLikeStorage $funct private function isIgnoredForUnusedParam(string $var_name): bool { - return strpos($var_name, '$_') === 0 || (strpos($var_name, '$unused') === 0 && $var_name !== '$unused'); + return str_starts_with($var_name, '$_') || (str_starts_with($var_name, '$unused') && $var_name !== '$unused'); } } diff --git a/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php b/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php index a1a10ebe46b..5e3f4151fb7 100644 --- a/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/InterfaceAnalyzer.php @@ -1,5 +1,7 @@ classlike_storage_provider->get($extended_interface_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -215,6 +217,10 @@ public function analyze(): void } } + $pseudo_methods = $class_storage->pseudo_methods + $class_storage->pseudo_static_methods; + + MethodComparator::comparePseudoMethods($pseudo_methods, $this->fq_class_name, $codebase, $class_storage); + $statements_analyzer = new StatementsAnalyzer($this, new NodeDataProvider()); $statements_analyzer->analyze($member_stmts, $interface_context, null, true); diff --git a/src/Psalm/Internal/Analyzer/IssueData.php b/src/Psalm/Internal/Analyzer/IssueData.php index 67c845a2134..e948337ce45 100644 --- a/src/Psalm/Internal/Analyzer/IssueData.php +++ b/src/Psalm/Internal/Analyzer/IssueData.php @@ -1,5 +1,7 @@ - */ - public ?array $taint_trace = null; - - /** - * @var ?list - */ - public ?array $other_references = null; - - /** - * @readonly - */ - public ?string $dupe_key = null; + public readonly string $link; /** * @param self::SEVERITY_* $severity @@ -104,47 +24,27 @@ final class IssueData * @param ?list $other_references */ public function __construct( - string $severity, - int $line_from, - int $line_to, - string $type, - string $message, - string $file_name, - string $file_path, - string $snippet, - string $selected_text, - int $from, - int $to, - int $snippet_from, - int $snippet_to, - int $column_from, - int $column_to, - int $shortcode = 0, - int $error_level = -1, - ?array $taint_trace = null, - ?array $other_references = null, - ?string $dupe_key = null + public string $severity, + public int $line_from, + public int $line_to, + public readonly string $type, + public readonly string $message, + public readonly string $file_name, + public readonly string $file_path, + public readonly string $snippet, + public readonly string $selected_text, + public int $from, + public int $to, + public int $snippet_from, + public int $snippet_to, + public readonly int $column_from, + public readonly int $column_to, + public readonly int $shortcode = 0, + public int $error_level = -1, + public ?array $taint_trace = null, + public ?array $other_references = null, + public readonly ?string $dupe_key = null, ) { - $this->severity = $severity; - $this->line_from = $line_from; - $this->line_to = $line_to; - $this->type = $type; - $this->message = $message; - $this->file_name = $file_name; - $this->file_path = $file_path; - $this->snippet = $snippet; - $this->selected_text = $selected_text; - $this->from = $from; - $this->to = $to; - $this->snippet_from = $snippet_from; - $this->snippet_to = $snippet_to; - $this->column_from = $column_from; - $this->column_to = $column_to; - $this->shortcode = $shortcode; - $this->error_level = $error_level; $this->link = $shortcode ? 'https://psalm.dev/' . str_pad((string) $shortcode, 3, "0", STR_PAD_LEFT) : ''; - $this->taint_trace = $taint_trace; - $this->other_references = $other_references; - $this->dupe_key = $dupe_key; } } diff --git a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php index 94e81f119a5..b487a5bc6b0 100644 --- a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -100,7 +104,7 @@ public static function checkStatic( Codebase $codebase, CodeLocation $code_location, array $suppressed_issues, - ?bool &$is_dynamic_this_method = false + ?bool &$is_dynamic_this_method = false, ): void { $codebase_methods = $codebase->methods; @@ -168,7 +172,7 @@ public static function checkMethodExists( CodeLocation $code_location, array $suppressed_issues, ?string $calling_method_id = null, - bool $with_pseudo = false + bool $with_pseudo = false, ): ?bool { if ($codebase->methods->methodExists( $method_id, @@ -212,7 +216,7 @@ public static function checkMethodExists( public static function isMethodVisible( MethodIdentifier $method_id, Context $context, - StatementsSource $source + StatementsSource $source, ): bool { $codebase = $source->getCodebase(); @@ -297,7 +301,7 @@ public static function isMethodVisible( */ public static function checkMethodSignatureMustOmitReturnType( MethodStorage $method_storage, - CodeLocation $code_location + CodeLocation $code_location, ): void { if ($method_storage->signature_return_type === null) { return; diff --git a/src/Psalm/Internal/Analyzer/MethodComparator.php b/src/Psalm/Internal/Analyzer/MethodComparator.php index 2fc7a24ca20..ff919b6c31e 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -1,5 +1,7 @@ name, @@ -236,6 +240,56 @@ public static function compare( return null; } + /** + * @param array $pseudo_methods + */ + public static function comparePseudoMethods( + array $pseudo_methods, + string $fq_class_name, + Codebase $codebase, + ClassLikeStorage $class_storage, + ): void { + foreach ($pseudo_methods as $pseudo_method_name => $pseudo_method_storage) { + $pseudo_method_id = new MethodIdentifier( + $fq_class_name, + $pseudo_method_name, + ); + + $overridden_method_ids = $codebase->methods->getOverriddenMethodIds($pseudo_method_id); + if (isset($class_storage->methods[$pseudo_method_id->method_name])) { + $overridden_method_ids[$class_storage->name] = $pseudo_method_id; + } + + if ($overridden_method_ids + && $pseudo_method_name !== '__construct' + && $pseudo_method_storage->location + ) { + foreach ($overridden_method_ids as $overridden_method_id) { + $parent_method_storage = $codebase->methods->getStorage($overridden_method_id); + + $overridden_fq_class_name = $overridden_method_id->fq_class_name; + + $parent_storage = $codebase->classlike_storage_provider->get($overridden_fq_class_name); + + self::compare( + $codebase, + null, + $class_storage, + $parent_storage, + $pseudo_method_storage, + $parent_method_storage, + $fq_class_name, + $pseudo_method_storage->visibility ?: 0, + $class_storage->location ?: $pseudo_method_storage->location, + $class_storage->suppressed_issues, + true, + false, + ); + } + } + } + } + /** * @param string[] $suppressed_issues */ @@ -252,7 +306,7 @@ private static function checkForObviousMethodMismatches( bool $prevent_abstract_override, bool $trait_mismatches_are_fatal, CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): void { if ($implementer_visibility > $guide_visibility) { if ($trait_mismatches_are_fatal @@ -359,7 +413,7 @@ private static function compareMethodParams( string $cased_implementer_method_id, bool $prevent_method_signature_mismatch, CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): void { // ignore errors from stubbed/out of project files $config = Config::getInstance(); @@ -451,7 +505,7 @@ private static function compareMethodParams( && $implementer_classlike_storage->user_defined && $implementer_param->location && $guide_method_storage->cased_name - && (strpos($guide_method_storage->cased_name, '__') !== 0 + && (!str_starts_with($guide_method_storage->cased_name, '__') || ($guide_classlike_storage->preserve_constructor_signature && $guide_method_storage->cased_name === '__construct')) && $config->isInProjectDirs( @@ -499,7 +553,10 @@ private static function compareMethodParams( } } - if ($implementer_param->signature_type) { + if ($guide_classlike_storage->user_defined + && $implementer_param->signature_type + && $guide_param->signature_type + ) { self::compareMethodSignatureParams( $codebase, $i, @@ -571,7 +628,7 @@ private static function compareMethodSignatureParams( string $cased_guide_method_id, string $cased_implementer_method_id, CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): void { $guide_param_signature_type = $guide_param->signature_type ? TypeExpander::expandUnion( @@ -735,7 +792,7 @@ private static function compareMethodDocblockParams( Union $guide_param_type, Union $implementer_param_type, CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): void { $implementer_method_storage_param_type = TypeExpander::expandUnion( $codebase, @@ -788,7 +845,7 @@ private static function compareMethodDocblockParams( $builder = $implementer_method_storage_param_type->getBuilder(); foreach ($builder->getAtomicTypes() as $k => $t) { if ($t instanceof TTemplateParam - && strpos($t->defining_class, 'fn-') === 0 + && str_starts_with($t->defining_class, 'fn-') ) { $builder->removeType($k); @@ -802,7 +859,7 @@ private static function compareMethodDocblockParams( $builder = $guide_method_storage_param_type->getBuilder(); foreach ($builder->getAtomicTypes() as $k => $t) { if ($t instanceof TTemplateParam - && strpos($t->defining_class, 'fn-') === 0 + && str_starts_with($t->defining_class, 'fn-') ) { $builder->removeType($k); @@ -869,6 +926,18 @@ private static function compareMethodDocblockParams( ), $suppressed_issues + $implementer_classlike_storage->suppressed_issues, ); + } elseif ($guide_class_name == $implementer_called_class_name) { + IssueBuffer::maybeAdd( + new MismatchingDocblockParamType( + 'Argument ' . ($i + 1) . ' of ' . $cased_implementer_method_id + . ' has wrong type \'' . + $implementer_method_storage_param_type->getId() . '\' in @method annotation, expecting \'' . + $guide_method_storage_param_type->getId() . '\'', + $implementer_method_storage->params[$i]->location + ?: $code_location, + ), + $suppressed_issues + $implementer_classlike_storage->suppressed_issues, + ); } else { IssueBuffer::maybeAdd( new ImplementedParamTypeMismatch( @@ -901,7 +970,7 @@ private static function compareMethodSignatureReturnTypes( string $implementer_called_class_name, string $cased_implementer_method_id, CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): void { $guide_signature_return_type = TypeExpander::expandUnion( $codebase, @@ -994,7 +1063,7 @@ private static function compareMethodDocblockReturnTypes( string $implementer_called_class_name, ?MethodIdentifier $implementer_declaring_method_id, CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): void { $implementer_method_storage_return_type = TypeExpander::expandUnion( $codebase, @@ -1096,6 +1165,17 @@ private static function compareMethodDocblockReturnTypes( ), $suppressed_issues + $implementer_classlike_storage->suppressed_issues, ); + } elseif ($guide_class_name == $implementer_called_class_name) { + IssueBuffer::maybeAdd( + new MismatchingDocblockReturnType( + 'The inherited return type \'' . $guide_method_storage_return_type->getId() + . '\' for ' . $cased_guide_method_id . ' is different to the corresponding ' + . '@method annotation \'' . $implementer_method_storage_return_type->getId() . '\'', + $implementer_method_storage->return_type_location + ?: $code_location, + ), + $suppressed_issues + $implementer_classlike_storage->suppressed_issues, + ); } else { IssueBuffer::maybeAdd( new ImplementedReturnTypeMismatch( @@ -1119,7 +1199,7 @@ private static function transformTemplates( array $template_extended_params, string $base_class_name, Union &$templated_type, - Codebase $codebase + Codebase $codebase, ): void { if (isset($template_extended_params[$base_class_name])) { $map = $template_extended_params[$base_class_name]; diff --git a/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php b/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php index d5f2ac9e4a7..6abd248d7b7 100644 --- a/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/NamespaceAnalyzer.php @@ -1,5 +1,7 @@ > */ - protected static array $public_namespace_constants = []; - - public function __construct(Namespace_ $namespace, FileAnalyzer $source) - { - $this->source = $source; - $this->namespace = $namespace; + private static array $public_namespace_constants = []; + + public function __construct( + private readonly Namespace_ $namespace, + /** + * @var FileAnalyzer + */ + protected SourceAnalyzer $source, + ) { $this->namespace_name = $this->namespace->name ? $this->namespace->name->toString() : ''; } @@ -211,7 +207,7 @@ public static function isWithinAny(string $calling_identifier, array $identifier */ public static function getNameSpaceRoot(string $fullyQualifiedClassName): string { - $root_namespace = preg_replace('/^([^\\\]+).*/', '$1', $fullyQualifiedClassName, 1); + $root_namespace = (string) preg_replace('/^([^\\\]+).*/', '$1', $fullyQualifiedClassName, 1); if ($root_namespace === "") { throw new InvalidArgumentException("Invalid classname \"$fullyQualifiedClassName\""); } @@ -244,7 +240,7 @@ public static function getIdentifierParts(string $identifier): array while (($pos = strpos($identifier, "\\")) !== false) { if ($pos > 0) { $part = substr($identifier, 0, $pos); - assert(is_string($part) && $part !== ""); + assert($part !== ""); $parts[] = $part; } $parts[] = "\\"; @@ -253,13 +249,13 @@ public static function getIdentifierParts(string $identifier): array if (($pos = strpos($identifier, "::")) !== false) { if ($pos > 0) { $part = substr($identifier, 0, $pos); - assert(is_string($part) && $part !== ""); + assert($part !== ""); $parts[] = $part; } $parts[] = "::"; $identifier = substr($identifier, $pos + 2); } - if ($identifier !== "" && $identifier !== false) { + if ($identifier !== "") { $parts[] = $identifier; } diff --git a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php index d5e9d090dc4..078c8403771 100644 --- a/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ProjectAnalyzer.php @@ -1,5 +1,7 @@ */ @@ -162,13 +164,6 @@ final class ProjectAnalyzer */ private array $to_refactor = []; - public ?ReportOptions $stdout_report_options = null; - - /** - * @var array - */ - public array $generated_report_options; - /** * @var array> */ @@ -206,11 +201,11 @@ final class ProjectAnalyzer public function __construct( Config $config, Providers $providers, - ?ReportOptions $stdout_report_options = null, - array $generated_report_options = [], - int $threads = 1, + public ?ReportOptions $stdout_report_options = null, + public array $generated_report_options = [], + public int $threads = 1, ?Progress $progress = null, - ?Codebase $codebase = null + ?Codebase $codebase = null, ) { if ($progress === null) { $progress = new VoidProgress(); @@ -231,16 +226,12 @@ public function __construct( $this->file_reference_provider = $providers->file_reference_provider; $this->progress = $progress; - $this->threads = $threads; $this->config = $config; $this->clearCacheDirectoryIfConfigOrComposerLockfileChanged(); $this->codebase = $codebase; - $this->stdout_report_options = $stdout_report_options; - $this->generated_report_options = $generated_report_options; - $this->config->processPluginFileExtensions($this); $file_extensions = $this->config->getFileExtensions(); @@ -248,7 +239,7 @@ public function __construct( $file_paths = $this->file_provider->getFilesInDir( $dir_name, $file_extensions, - [$this->config, 'isInProjectDirs'], + $this->config->isInProjectDirs(...), ); foreach ($file_paths as $file_path) { @@ -260,7 +251,7 @@ public function __construct( $file_paths = $this->file_provider->getFilesInDir( $dir_name, $file_extensions, - [$this->config, 'isInExtraDirs'], + $this->config->isInExtraDirs(...), ); foreach ($file_paths as $file_path) { @@ -333,7 +324,7 @@ public static function getFileReportOptions(array $report_file_paths, bool $show foreach ($report_file_paths as $report_file_path) { foreach ($mapping as $extension => $type) { - if (substr($report_file_path, -strlen($extension)) === $extension) { + if (str_ends_with($report_file_path, $extension)) { $o = new ReportOptions(); $o->format = $type; @@ -497,6 +488,7 @@ public function check(string $base_dir, bool $is_diff = false): void $this->codebase->infer_types_from_usage = true; } else { + $this->codebase->diff_run = true; $this->progress->debug(count($diff_files) . ' changed files: ' . "\n"); $this->progress->debug(' ' . implode("\n ", $diff_files) . "\n"); @@ -584,7 +576,7 @@ public function interpretRefactors(): void && $destination_pos === (strlen($destination) - 1) ) { foreach ($this->codebase->classlike_storage_provider->getAll() as $class_storage) { - if (strpos($source, substr($class_storage->name, 0, $source_pos)) === 0) { + if (str_starts_with($source, substr($class_storage->name, 0, $source_pos))) { $this->to_refactor[$class_storage->name] = substr($destination, 0, -1) . substr($class_storage->name, $source_pos); } @@ -910,7 +902,7 @@ public function checkDir(string $dir_name): void private function checkDirWithConfig(string $dir_name, Config $config, bool $allow_non_project_files = false): void { $file_extensions = $config->getFileExtensions(); - $filter = $allow_non_project_files ? null : [$this->config, 'isInProjectDirs']; + $filter = $allow_non_project_files ? null : $this->config->isInProjectDirs(...); $file_paths = $this->file_provider->getFilesInDir( $dir_name, @@ -940,7 +932,7 @@ public function addExtraFile(string $file_path): void /** * @return list */ - protected function getDiffFiles(): array + private function getDiffFiles(): array { if (!$this->parser_cache_provider || !$this->project_cache_provider) { throw new UnexpectedValueException('Parser cache provider cannot be null here'); @@ -1022,6 +1014,9 @@ public function checkFile(string $file_path): void */ public function checkPaths(array $paths_to_check): void { + $this->progress->write($this->generatePHPVersionMessage()); + $this->progress->startScanningFiles(); + $this->config->visitPreloadedStubFiles($this->codebase, $this->progress); $this->visitAutoloadFiles(); @@ -1041,9 +1036,6 @@ public function checkPaths(array $paths_to_check): void $this->file_reference_provider->loadReferenceCache(); - $this->progress->write($this->generatePHPVersionMessage()); - $this->progress->startScanningFiles(); - $this->config->initializePlugins($this); @@ -1137,7 +1129,7 @@ public function isDirectory(string $file_path): bool public function alterCodeAfterCompletion( bool $dry_run = false, - bool $safe_types = false + bool $safe_types = false, ): void { $this->codebase->alter_code = true; $this->codebase->infer_types_from_usage = true; @@ -1181,8 +1173,7 @@ public function setPhpVersion(string $version, string $source): void $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(); + // reset parser when php version changes StatementsProvider::clearParser(); } @@ -1251,7 +1242,7 @@ public function getMethodMutations( MethodIdentifier $original_method_id, Context $this_context, string $root_file_path, - string $root_file_name + string $root_file_name, ): void { $fq_class_name = $original_method_id->fq_class_name; @@ -1298,7 +1289,7 @@ public function getMethodMutations( public function getFunctionLikeAnalyzer( MethodIdentifier $method_id, - string $file_path + string $file_path, ): ?FunctionLikeAnalyzer { $file_analyzer = new FileAnalyzer( $this, diff --git a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php index 808010d09bc..25cca24d9ec 100644 --- a/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/ScopeAnalyzer.php @@ -1,5 +1,7 @@ expr instanceof PhpParser\Node\Expr\Exit_) + ($stmt instanceof PhpParser\Node\Stmt\Expression + && ($stmt->expr instanceof PhpParser\Node\Expr\Exit_ + || $stmt->expr instanceof PhpParser\Node\Expr\Throw_)) ) { if (!$return_is_exit && $stmt instanceof PhpParser\Node\Stmt\Return_) { $stmt_return_type = null; @@ -83,7 +86,7 @@ public static function getControlActions( if ($stmt instanceof PhpParser\Node\Stmt\Continue_) { $count = !$stmt->num ? 1 - : ($stmt->num instanceof PhpParser\Node\Scalar\LNumber ? $stmt->num->value : null); + : ($stmt->num instanceof PhpParser\Node\Scalar\Int_ ? $stmt->num->value : null); if ($break_types && $count !== null && count($break_types) >= $count) { /** @psalm-suppress InvalidArrayOffset Some int-range improvements are needed */ @@ -100,7 +103,7 @@ public static function getControlActions( if ($stmt instanceof PhpParser\Node\Stmt\Break_) { $count = !$stmt->num ? 1 - : ($stmt->num instanceof PhpParser\Node\Scalar\LNumber ? $stmt->num->value : null); + : ($stmt->num instanceof PhpParser\Node\Scalar\Int_ ? $stmt->num->value : null); if ($break_types && $count !== null && count($break_types) >= $count) { /** @psalm-suppress InvalidArrayOffset Some int-range improvements are needed */ @@ -406,9 +409,9 @@ public static function onlyThrowsOrExits(NodeTypeProvider $type_provider, array for ($i = count($stmts) - 1; $i >= 0; --$i) { $stmt = $stmts[$i]; - if ($stmt instanceof PhpParser\Node\Stmt\Throw_ - || ($stmt instanceof PhpParser\Node\Stmt\Expression - && $stmt->expr instanceof PhpParser\Node\Expr\Exit_) + if ($stmt instanceof PhpParser\Node\Stmt\Expression + && ($stmt->expr instanceof PhpParser\Node\Expr\Exit_ + || $stmt->expr instanceof PhpParser\Node\Expr\Throw_) ) { return true; } @@ -436,7 +439,7 @@ public static function onlyThrows(array $stmts): bool } foreach ($stmts as $stmt) { - if ($stmt instanceof PhpParser\Node\Stmt\Throw_) { + if ($stmt instanceof PhpParser\Node\Stmt\Expression && $stmt->expr instanceof PhpParser\Node\Expr\Throw_) { return true; } } diff --git a/src/Psalm/Internal/Analyzer/SourceAnalyzer.php b/src/Psalm/Internal/Analyzer/SourceAnalyzer.php index 705176d6818..e0f3c463b30 100644 --- a/src/Psalm/Internal/Analyzer/SourceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/SourceAnalyzer.php @@ -1,5 +1,7 @@ break_types[] = 'loop'; @@ -156,10 +157,10 @@ static function (Clause $c) use ($mixed_var_ids): bool { $do_context->loop_scope = null; - $context->vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $do_context->vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$do_context->vars_possibly_in_scope, + ]; if ($context->collect_exceptions) { $context->mergeExceptions($inner_loop_context); @@ -172,7 +173,7 @@ private static function analyzeDoNaively( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Stmt\Do_ $stmt, Context $context, - LoopScope $loop_scope + LoopScope $loop_scope, ): void { $do_context = clone $context; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php index dbad2cd129a..b8e95b7d9e2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForAnalyzer.php @@ -1,5 +1,7 @@ assigned_var_ids; $context->assigned_var_ids = []; @@ -168,10 +170,10 @@ public static function analyze( $for_context->loop_scope = null; if ($can_leave_loop) { - $context->vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $for_context->vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$for_context->vars_possibly_in_scope, + ]; } elseif ($pre_context) { $context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php index 19099c9c247..07ac96df403 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/ForeachAnalyzer.php @@ -1,5 +1,7 @@ loop_scope = null; - $context->vars_possibly_in_scope = array_merge( - $foreach_context->vars_possibly_in_scope, - $context->vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$foreach_context->vars_possibly_in_scope, + ...$context->vars_possibly_in_scope, + ]; if ($context->collect_exceptions) { $context->mergeExceptions($foreach_context); @@ -410,7 +410,7 @@ public static function checkIteratorType( Context $context, ?Union &$key_type, ?Union &$value_type, - bool &$always_non_empty_array + bool &$always_non_empty_array, ): ?bool { if ($iterator_type->isNull()) { IssueBuffer::maybeAdd( @@ -472,18 +472,13 @@ public static function checkIteratorType( if ($iterator_atomic_type instanceof TArray || $iterator_atomic_type instanceof TKeyedArray - || $iterator_atomic_type instanceof TList ) { - if ($iterator_atomic_type instanceof TList) { - $iterator_atomic_type = $iterator_atomic_type->getKeyedArray(); - } if ($iterator_atomic_type instanceof TKeyedArray) { if (!$iterator_atomic_type->isNonEmpty()) { $always_non_empty_array = false; } $iterator_atomic_type = $iterator_atomic_type->getGenericArrayType( - true, ExpressionIdentifier::getExtendedVarId( $expr, $statements_analyzer->getFQCLN(), @@ -553,10 +548,10 @@ public static function checkIteratorType( } } elseif ($iterator_atomic_type instanceof TIterable) { if ($iterator_atomic_type->extra_types) { - $iterator_atomic_types = array_merge( - [$iterator_atomic_type->setIntersectionTypes([])], - $iterator_atomic_type->extra_types, - ); + $iterator_atomic_types = [ + $iterator_atomic_type->setIntersectionTypes([]), + ...$iterator_atomic_type->extra_types, + ]; } else { $iterator_atomic_types = [$iterator_atomic_type]; } @@ -736,13 +731,13 @@ public static function handleIterable( ?Union &$key_type, ?Union &$value_type, bool &$has_valid_iterator, - array &$invalid_iterator_types = [] + array &$invalid_iterator_types = [], ): void { if ($iterator_atomic_type->extra_types) { - $iterator_atomic_types = array_merge( - [$iterator_atomic_type->setIntersectionTypes([])], - $iterator_atomic_type->extra_types, - ); + $iterator_atomic_types = [ + $iterator_atomic_type->setIntersectionTypes([]), + ...$iterator_atomic_type->extra_types, + ]; } else { $iterator_atomic_types = [$iterator_atomic_type]; } @@ -755,8 +750,6 @@ public static function handleIterable( throw new UnexpectedValueException('Shouldn’t get a generic param here'); } - - if ($iterator_atomic_type instanceof TIterable || (strtolower($iterator_atomic_type->value) === 'traversable' || $codebase->classImplements( @@ -1007,7 +1000,7 @@ public static function getKeyValueParamsForTraversableObject( Atomic $iterator_atomic_type, Codebase $codebase, ?Union &$key_type, - ?Union &$value_type + ?Union &$value_type, ): void { if ($iterator_atomic_type instanceof TIterable || ($iterator_atomic_type instanceof TGenericObject @@ -1084,7 +1077,7 @@ private static function getFakeMethodCallType( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $foreach_expr, Context $context, - string $method_name + string $method_name, ): ?Union { $old_data_provider = $statements_analyzer->node_data; @@ -1143,7 +1136,7 @@ private static function getExtendedType( string $calling_class, array $template_extended_params, ?array $class_template_types = null, - ?array $calling_type_params = null + ?array $calling_type_params = null, ): ?Union { if ($calling_class === $template_class) { if (isset($class_template_types[$template_name]) && $calling_type_params) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php index c98700f071a..e90c6dca7ce 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfConditionalAnalyzer.php @@ -1,5 +1,7 @@ node_data->getType($stmt); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php index f1d9e5a9fd0..568a19e1a88 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -198,22 +200,22 @@ public static function analyze( if ($has_leaving_statements) { if ($else_context->loop_scope) { if (!$has_continue_statement && !$has_break_statement) { - $if_scope->new_vars_possibly_in_scope = array_merge( - $vars_possibly_in_scope, - $if_scope->new_vars_possibly_in_scope, - ); + $if_scope->new_vars_possibly_in_scope = [ + ...$vars_possibly_in_scope, + ...$if_scope->new_vars_possibly_in_scope, + ]; } - $else_context->loop_scope->vars_possibly_in_scope = array_merge( - $vars_possibly_in_scope, - $else_context->loop_scope->vars_possibly_in_scope, - ); + $else_context->loop_scope->vars_possibly_in_scope = [ + ...$vars_possibly_in_scope, + ...$else_context->loop_scope->vars_possibly_in_scope, + ]; } } else { - $if_scope->new_vars_possibly_in_scope = array_merge( - $vars_possibly_in_scope, - $if_scope->new_vars_possibly_in_scope, - ); + $if_scope->new_vars_possibly_in_scope = [ + ...$vars_possibly_in_scope, + ...$if_scope->new_vars_possibly_in_scope, + ]; $if_scope->possibly_assigned_var_ids = array_merge( $possibly_assigned_var_ids, diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php index e1378100049..50bc88099f7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/ElseIfAnalyzer.php @@ -1,5 +1,7 @@ if_context; $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; $assigned_in_conditional_var_ids = $if_conditional_scope->assigned_in_conditional_var_ids; - } catch (ScopeAnalysisException $e) { + } catch (ScopeAnalysisException) { return false; } @@ -186,7 +188,7 @@ public static function analyze( $negated_elseif_types = Algebra::getTruthsFromFormula( Algebra::negateFormula($elseif_clauses), ); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $reconcilable_elseif_types = []; $negated_elseif_types = []; } @@ -371,25 +373,25 @@ public static function analyze( if ($has_leaving_statements && $elseif_context->loop_scope) { if (!$has_continue_statement && !$has_break_statement) { - $if_scope->new_vars_possibly_in_scope = array_merge( - $vars_possibly_in_scope, - $if_scope->new_vars_possibly_in_scope, - ); + $if_scope->new_vars_possibly_in_scope = [ + ...$vars_possibly_in_scope, + ...$if_scope->new_vars_possibly_in_scope, + ]; $if_scope->possibly_assigned_var_ids = array_merge( $possibly_assigned_var_ids, $if_scope->possibly_assigned_var_ids, ); } - $elseif_context->loop_scope->vars_possibly_in_scope = array_merge( - $vars_possibly_in_scope, - $elseif_context->loop_scope->vars_possibly_in_scope, - ); + $elseif_context->loop_scope->vars_possibly_in_scope = [ + ...$vars_possibly_in_scope, + ...$elseif_context->loop_scope->vars_possibly_in_scope, + ]; } elseif (!$has_leaving_statements) { - $if_scope->new_vars_possibly_in_scope = array_merge( - $vars_possibly_in_scope, - $if_scope->new_vars_possibly_in_scope, - ); + $if_scope->new_vars_possibly_in_scope = [ + ...$vars_possibly_in_scope, + ...$if_scope->new_vars_possibly_in_scope, + ]; $if_scope->possibly_assigned_var_ids = array_merge( $possibly_assigned_var_ids, $if_scope->possibly_assigned_var_ids, @@ -405,7 +407,7 @@ public static function analyze( $if_scope->negated_clauses = Algebra::simplifyCNF( [...$if_scope->negated_clauses, ...Algebra::negateFormula($elseif_clauses)], ); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $if_scope->negated_clauses = []; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php index 2a2be74606f..3a23be749c1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElse/IfAnalyzer.php @@ -1,5 +1,7 @@ cond_referenced_var_ids; @@ -141,10 +143,10 @@ public static function analyze( $if_context->reconciled_expression_clauses = []; - $outer_context->vars_possibly_in_scope = array_merge( - $if_context->vars_possibly_in_scope, - $outer_context->vars_possibly_in_scope, - ); + $outer_context->vars_possibly_in_scope = [ + ...$if_context->vars_possibly_in_scope, + ...$outer_context->vars_possibly_in_scope, + ]; $old_if_context = clone $if_context; @@ -289,10 +291,10 @@ public static function analyze( $if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope; } - $if_context->loop_scope->vars_possibly_in_scope = array_merge( - $vars_possibly_in_scope, - $if_context->loop_scope->vars_possibly_in_scope, - ); + $if_context->loop_scope->vars_possibly_in_scope = [ + ...$vars_possibly_in_scope, + ...$if_context->loop_scope->vars_possibly_in_scope, + ]; } elseif (!$has_leaving_statements) { $if_scope->new_vars_possibly_in_scope = $vars_possibly_in_scope; } @@ -319,7 +321,7 @@ public static function addConditionallyAssignedVarsToContext( PhpParser\Node\Expr $cond, Context $post_leaving_if_context, Context $post_if_context, - array $assigned_in_conditional_var_ids + array $assigned_in_conditional_var_ids, ): void { // this filters out coercions to expected types in ArgumentAnalyzer $assigned_in_conditional_var_ids = array_filter($assigned_in_conditional_var_ids); @@ -423,7 +425,7 @@ public static function updateIfScope( array $assigned_var_ids, array $possibly_assigned_var_ids, array $newly_reconciled_var_ids, - bool $update_new_vars = true + bool $update_new_vars = true, ): void { $redefined_vars = $if_context->getRedefinedVars($outer_context->vars_in_scope); diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php index 99ef6c45291..d7fa1aed286 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/IfElseAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -110,7 +112,7 @@ public static function analyze( // this is the context for stuff that happens after the `if` block $post_if_context = $if_conditional_scope->post_if_context; $assigned_in_conditional_var_ids = $if_conditional_scope->assigned_in_conditional_var_ids; - } catch (ScopeAnalysisException $e) { + } catch (ScopeAnalysisException) { return false; } @@ -200,7 +202,7 @@ public static function analyze( try { $if_scope->negated_clauses = Algebra::negateFormula($if_clauses); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { try { $if_scope->negated_clauses = FormulaGenerator::getFormula( $cond_object_id, @@ -211,7 +213,7 @@ public static function analyze( $codebase, false, ); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $if_scope->negated_clauses = []; } } @@ -361,15 +363,15 @@ public static function analyze( ); } - $context->vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $if_scope->new_vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$if_scope->new_vars_possibly_in_scope, + ]; - $context->possibly_assigned_var_ids = array_merge( - $context->possibly_assigned_var_ids, - $if_scope->possibly_assigned_var_ids ?: [], - ); + $context->possibly_assigned_var_ids = [ + ...$context->possibly_assigned_var_ids, + ...$if_scope->possibly_assigned_var_ids ?: [], + ]; // vars can only be defined/redefined if there was an else (defined in every block) $context->assigned_var_ids = array_merge( diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php index 7d36fd3399b..84d2961e979 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/LoopAnalyzer.php @@ -1,5 +1,7 @@ vars_possibly_in_scope = array_merge( - $continue_context->vars_possibly_in_scope, - $loop_parent_context->vars_possibly_in_scope, - ); + $loop_parent_context->vars_possibly_in_scope = [ + ...$continue_context->vars_possibly_in_scope, + ...$loop_parent_context->vars_possibly_in_scope, + ]; } else { $original_parent_context = clone $loop_parent_context; @@ -268,10 +270,10 @@ public static function analyze( $continue_context->has_returned = false; - $loop_parent_context->vars_possibly_in_scope = array_merge( - $continue_context->vars_possibly_in_scope, - $loop_parent_context->vars_possibly_in_scope, - ); + $loop_parent_context->vars_possibly_in_scope = [ + ...$continue_context->vars_possibly_in_scope, + ...$loop_parent_context->vars_possibly_in_scope, + ]; // if there are no changes to the types, no need to re-examine if (!$has_changes) { @@ -440,10 +442,10 @@ public static function analyze( $loop_parent_context->removeVarFromConflictingClauses($var_id); } else { $loop_parent_context->vars_in_scope[$var_id] = - $loop_parent_context->vars_in_scope[$var_id]->setParentNodes(array_merge( - $loop_parent_context->vars_in_scope[$var_id]->parent_nodes, - $continue_context->vars_in_scope[$var_id]->parent_nodes, - )) + $loop_parent_context->vars_in_scope[$var_id]->setParentNodes([ + ...$loop_parent_context->vars_in_scope[$var_id]->parent_nodes, + ...$continue_context->vars_in_scope[$var_id]->parent_nodes, + ]) ; } } @@ -455,7 +457,7 @@ public static function analyze( try { $negated_pre_condition_clauses = Algebra::negateFormula(array_merge(...$pre_condition_clauses)); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $negated_pre_condition_clauses = []; } @@ -526,7 +528,7 @@ private static function updateLoopScopeContexts( LoopScope $loop_scope, Context $loop_context, Context $continue_context, - Context $pre_outer_context + Context $pre_outer_context, ): void { if (!in_array(ScopeAnalyzer::ACTION_CONTINUE, $loop_scope->final_actions, true)) { $loop_context->vars_in_scope = $pre_outer_context->vars_in_scope; @@ -554,10 +556,10 @@ private static function updateLoopScopeContexts( } // merge vars possibly in scope at the end of each loop - $loop_context->vars_possibly_in_scope = array_merge( - $loop_context->vars_possibly_in_scope, - $loop_scope->vars_possibly_in_scope, - ); + $loop_context->vars_possibly_in_scope = [ + ...$loop_context->vars_possibly_in_scope, + ...$loop_scope->vars_possibly_in_scope, + ]; } /** @@ -570,7 +572,7 @@ private static function applyPreConditionToLoopContext( array $pre_condition_clauses, Context $loop_context, Context $outer_context, - bool $is_do + bool $is_do, ): array { $pre_referenced_var_ids = $loop_context->cond_referenced_var_ids; $loop_context->cond_referenced_var_ids = []; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php index 73240624c8e..ee9d7cb1822 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -217,10 +219,10 @@ public static function analyze( $context->assigned_var_ids += $switch_scope->new_assigned_var_ids; } - $context->vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $switch_scope->new_vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$switch_scope->new_vars_possibly_in_scope, + ]; //a switch can't return in all options without a default $context->has_returned = $all_options_returned && $has_default; diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php index 54b7e7f3f01..33fa2b39dff 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/SwitchCaseAnalyzer.php @@ -1,5 +1,7 @@ cond->getAttributes(), @@ -247,8 +249,8 @@ public static function analyze( $case_equality_expr = new VirtualFuncCall( new VirtualFullyQualified(['rand']), [ - new VirtualArg(new VirtualLNumber(0)), - new VirtualArg(new VirtualLNumber(1)), + new VirtualArg(new VirtualInt(0)), + new VirtualArg(new VirtualInt(1)), ], $case->getAttributes(), ); @@ -292,8 +294,8 @@ public static function analyze( $case_or_default_equality_expr = new VirtualFuncCall( new VirtualFullyQualified(['rand']), [ - new VirtualArg(new VirtualLNumber(0)), - new VirtualArg(new VirtualLNumber(1)), + new VirtualArg(new VirtualInt(0)), + new VirtualArg(new VirtualInt(1)), ], $case->getAttributes(), ); @@ -437,7 +439,7 @@ public static function analyze( if ($case_clauses && $case_equality_expr) { try { $negated_case_clauses = Algebra::negateFormula($case_clauses); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $case_equality_expr_id = spl_object_id($case_equality_expr); try { @@ -451,7 +453,7 @@ public static function analyze( false, false, ); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $negated_case_clauses = []; } } @@ -557,7 +559,7 @@ private static function handleNonReturningCase( Context $case_context, Context $original_context, string $case_exit_type, - SwitchScope $switch_scope + SwitchScope $switch_scope, ): ?bool { if (!$case->cond && $switch_var_id @@ -634,13 +636,10 @@ private static function handleNonReturningCase( } } - $switch_scope->new_vars_possibly_in_scope = array_merge( - array_diff_key( - $case_context->vars_possibly_in_scope, - $context->vars_possibly_in_scope, - ), - $switch_scope->new_vars_possibly_in_scope, - ); + $switch_scope->new_vars_possibly_in_scope = [...array_diff_key( + $case_context->vars_possibly_in_scope, + $context->vars_possibly_in_scope, + ), ...$switch_scope->new_vars_possibly_in_scope]; } } @@ -653,7 +652,7 @@ private static function handleNonReturningCase( private static function simplifyCaseEqualityExpression( PhpParser\Node\Expr $case_equality_expr, - PhpParser\Node\Expr\Variable $var + PhpParser\Node\Expr\Variable $var, ): ?PhpParser\Node\Expr\FuncCall { if ($case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { $nested_or_options = self::getOptionsFromNestedOr($case_equality_expr, $var); @@ -691,13 +690,13 @@ private static function simplifyCaseEqualityExpression( } /** - * @param array $in_array_values - * @return ?array + * @param array $in_array_values + * @return ?array */ private static function getOptionsFromNestedOr( PhpParser\Node\Expr $case_equality_expr, PhpParser\Node\Expr\Variable $var, - array $in_array_values = [] + array $in_array_values = [], ): ?array { if ($case_equality_expr instanceof PhpParser\Node\Expr\BinaryOp\Identical && $case_equality_expr->left instanceof PhpParser\Node\Expr\Variable diff --git a/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php index fab72f60413..2f88ac31e75 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Block/TryAnalyzer.php @@ -1,5 +1,7 @@ cond instanceof PhpParser\Node\Expr\ConstFetch && $stmt->cond->name->getParts() === ['true']) @@ -102,10 +103,10 @@ public static function analyze( $while_context->loop_scope = null; if ($can_leave_loop) { - $context->vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $while_context->vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$while_context->vars_possibly_in_scope, + ]; } elseif ($pre_context) { $context->vars_possibly_in_scope = $pre_context->vars_possibly_in_scope; } @@ -121,7 +122,7 @@ public static function analyze( * @return list */ public static function getAndExpressions( - PhpParser\Node\Expr $expr + PhpParser\Node\Expr $expr, ): array { if ($expr instanceof PhpParser\Node\Expr\BinaryOp\BooleanAnd) { return [...self::getAndExpressions($expr->left), ...self::getAndExpressions($expr->right)]; diff --git a/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php index e1b7ff72024..52eede08d3a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/BreakAnalyzer.php @@ -1,5 +1,7 @@ loop_scope; @@ -27,7 +29,7 @@ public static function analyze( if ($loop_scope) { if ($context->break_types && end($context->break_types) === 'switch' - && (!$stmt->num instanceof PhpParser\Node\Scalar\LNumber || $stmt->num->value < 2) + && (!$stmt->num instanceof PhpParser\Node\Scalar\Int_ || $stmt->num->value < 2) ) { $loop_scope->final_actions[] = ScopeAnalyzer::ACTION_LEAVE_SWITCH; } else { diff --git a/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php index def2aa47287..a41a1bca392 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ContinueAnalyzer.php @@ -1,5 +1,7 @@ num instanceof PhpParser\Node\Scalar\LNumber? $stmt->num->value : 1; + $count = $stmt->num instanceof PhpParser\Node\Scalar\Int_? $stmt->num->value : 1; $loop_scope = $context->loop_scope; diff --git a/src/Psalm/Internal/Analyzer/Statements/DeclareAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/DeclareAnalyzer.php index fed7eb3e1f1..2eb42eb876b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/DeclareAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/DeclareAnalyzer.php @@ -1,5 +1,7 @@ declares as $declaration) { $declaration_key = (string) $declaration->key; @@ -55,7 +57,7 @@ public static function analyze( private static function analyzeStrictTypesDeclaration( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Stmt\DeclareDeclare $declaration, - Context $context + Context $context, ): void { if (!$declaration->value instanceof PhpParser\Node\Scalar\LNumber || !in_array($declaration->value->value, [0, 1], true) @@ -78,7 +80,7 @@ private static function analyzeStrictTypesDeclaration( private static function analyzeTicksDeclaration( StatementsAnalyzer $statements_analyzer, - PhpParser\Node\Stmt\DeclareDeclare $declaration + PhpParser\Node\Stmt\DeclareDeclare $declaration, ): void { if (!$declaration->value instanceof PhpParser\Node\Scalar\LNumber) { IssueBuffer::maybeAdd( @@ -93,7 +95,7 @@ private static function analyzeTicksDeclaration( private static function analyzeEncodingDeclaration( StatementsAnalyzer $statements_analyzer, - PhpParser\Node\Stmt\DeclareDeclare $declaration + PhpParser\Node\Stmt\DeclareDeclare $declaration, ): void { if (!$declaration->value instanceof PhpParser\Node\Scalar\String_) { IssueBuffer::maybeAdd( diff --git a/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php index b21ae4647c5..f04d8c131e8 100644 --- a/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/EchoAnalyzer.php @@ -1,5 +1,7 @@ items) === 0) { @@ -242,13 +243,11 @@ public static function analyze( } /** - * @param string|int $literal_array_key - * @return false|int * @psalm-assert-if-false !numeric $literal_array_key */ public static function getLiteralArrayKeyInt( - $literal_array_key - ) { + string|int $literal_array_key, + ): false|int { if (is_int($literal_array_key)) { return $literal_array_key; } @@ -277,8 +276,8 @@ private static function analyzeArrayItem( StatementsAnalyzer $statements_analyzer, Context $context, ArrayCreationInfo $array_creation_info, - PhpParser\Node\Expr\ArrayItem $item, - Codebase $codebase + PhpParser\Node\ArrayItem $item, + Codebase $codebase, ): void { if ($item->unpack) { if (ExpressionAnalyzer::analyze($statements_analyzer, $item->value, $context) === false) { @@ -550,17 +549,14 @@ private static function analyzeArrayItem( private static function handleUnpackedArray( StatementsAnalyzer $statements_analyzer, ArrayCreationInfo $array_creation_info, - PhpParser\Node\Expr\ArrayItem $item, + PhpParser\Node\ArrayItem $item, Union $unpacked_array_type, - Codebase $codebase + Codebase $codebase, ): void { $all_non_empty = true; $has_possibly_undefined = false; foreach ($unpacked_array_type->getAtomicTypes() as $unpacked_atomic_type) { - if ($unpacked_atomic_type instanceof TList) { - $unpacked_atomic_type = $unpacked_atomic_type->getKeyedArray(); - } if ($unpacked_atomic_type instanceof TKeyedArray) { foreach ($unpacked_atomic_type->properties as $key => $property_value) { if ($property_value->possibly_undefined) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php index 35b2161683e..f96db30833b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ArrayCreationInfo.php @@ -1,5 +1,7 @@ getArgs()[0]->value) ? ExpressionIdentifier::getExtendedVarId( @@ -869,7 +872,7 @@ private static function processIrreconcilableFunctionCall( PhpParser\Node\Expr $expr, StatementsAnalyzer $source, Codebase $codebase, - bool $negate + bool $negate, ): void { if ($first_var_type->hasMixed()) { return; @@ -930,10 +933,10 @@ private static function processIrreconcilableFunctionCall( * @param PhpParser\Node\Expr\FuncCall|PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $expr * @return list>>> */ - protected static function processCustomAssertion( + private static function processCustomAssertion( PhpParser\Node\Expr $expr, ?string $this_class_name, - FileSource $source + FileSource $source, ): array { if (!$source instanceof StatementsAnalyzer) { return []; @@ -1019,7 +1022,7 @@ protected static function processCustomAssertion( $if_types[$var_id] = [[$assertion->rule[0]]]; } } elseif (is_string($assertion->var_id)) { - $is_function = substr($assertion->var_id, -2) === '()'; + $is_function = str_ends_with($assertion->var_id, '()'); $exploded_id = explode('->', $assertion->var_id); $var_id = $exploded_id[0] ?? null; $property = $exploded_id[1] ?? null; @@ -1075,7 +1078,7 @@ protected static function processCustomAssertion( } elseif (!$expr instanceof PhpParser\Node\Expr\FuncCall) { $assertion_var_id = $assertion->var_id; - if (strpos($assertion_var_id, 'self::') === 0) { + if (str_starts_with($assertion_var_id, 'self::')) { $assertion_var_id = $this_class_name.'::'.substr($assertion_var_id, 6); } } else { @@ -1149,7 +1152,7 @@ protected static function processCustomAssertion( $if_types[$var_id] = [[$assertion->rule[0]->getNegation()]]; } } elseif (is_string($assertion->var_id)) { - $is_function = substr($assertion->var_id, -2) === '()'; + $is_function = str_ends_with($assertion->var_id, '()'); $exploded_id = explode('->', $assertion->var_id); $var_id = $exploded_id[0] ?? null; $property = $exploded_id[1] ?? null; @@ -1206,7 +1209,7 @@ protected static function processCustomAssertion( $if_types[$assertion_var_id] = [[$rule]]; } elseif (!$expr instanceof PhpParser\Node\Expr\FuncCall) { $var_id = $assertion->var_id; - if (strpos($var_id, 'self::') === 0) { + if (str_starts_with($var_id, 'self::')) { $var_id = $this_class_name.'::'.substr($var_id, 6); } $if_types[$var_id] = [[$assertion->rule[0]->getNegation()]]; @@ -1232,10 +1235,10 @@ protected static function processCustomAssertion( /** * @return list */ - protected static function getInstanceOfAssertions( + private static function getInstanceOfAssertions( PhpParser\Node\Expr\Instanceof_ $stmt, ?string $this_class_name, - FileSource $source + FileSource $source, ): array { if ($stmt->class instanceof PhpParser\Node\Name) { if (!in_array(strtolower($stmt->class->getFirst()), ['self', 'static', 'parent'], true)) { @@ -1297,9 +1300,9 @@ protected static function getInstanceOfAssertions( /** * @param Identical|Equal|NotIdentical|NotEqual $conditional */ - protected static function hasNullVariable( + private static function hasNullVariable( PhpParser\Node\Expr\BinaryOp $conditional, - FileSource $source + FileSource $source, ): ?int { if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch && strtolower($conditional->right->name->getFirst()) === 'null' @@ -1327,7 +1330,7 @@ protected static function hasNullVariable( * @param Identical|Equal|NotIdentical|NotEqual $conditional */ public static function hasFalseVariable( - PhpParser\Node\Expr\BinaryOp $conditional + PhpParser\Node\Expr\BinaryOp $conditional, ): ?int { if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch && strtolower($conditional->right->name->getFirst()) === 'false' @@ -1348,7 +1351,7 @@ public static function hasFalseVariable( * @param Identical|Equal|NotIdentical|NotEqual $conditional */ public static function hasTrueVariable( - PhpParser\Node\Expr\BinaryOp $conditional + PhpParser\Node\Expr\BinaryOp $conditional, ): ?int { if ($conditional->right instanceof PhpParser\Node\Expr\ConstFetch && strtolower($conditional->right->name->getFirst()) === 'true' @@ -1368,8 +1371,8 @@ public static function hasTrueVariable( /** * @param Identical|Equal|NotIdentical|NotEqual $conditional */ - protected static function hasEmptyArrayVariable( - PhpParser\Node\Expr\BinaryOp $conditional + private static function hasEmptyArrayVariable( + PhpParser\Node\Expr\BinaryOp $conditional, ): ?int { if ($conditional->right instanceof PhpParser\Node\Expr\Array_ && !$conditional->right->items @@ -1390,9 +1393,9 @@ protected static function hasEmptyArrayVariable( * @param Identical|Equal|NotIdentical|NotEqual $conditional * @return false|int */ - protected static function hasGetTypeCheck( - PhpParser\Node\Expr\BinaryOp $conditional - ) { + private static function hasGetTypeCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + ): bool|int { if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall && $conditional->right->name instanceof PhpParser\Node\Name && strtolower($conditional->right->name->getFirst()) === 'gettype' @@ -1418,9 +1421,9 @@ protected static function hasGetTypeCheck( * @param Identical|Equal|NotIdentical|NotEqual $conditional * @return false|int */ - protected static function hasGetDebugTypeCheck( - PhpParser\Node\Expr\BinaryOp $conditional - ) { + private static function hasGetDebugTypeCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + ): bool|int { if ($conditional->right instanceof PhpParser\Node\Expr\FuncCall && $conditional->right->name instanceof PhpParser\Node\Name && strtolower($conditional->right->name->getFirst()) === 'get_debug_type' @@ -1448,10 +1451,10 @@ protected static function hasGetDebugTypeCheck( * @param Identical|Equal|NotIdentical|NotEqual $conditional * @return false|int */ - protected static function hasGetClassCheck( + private static function hasGetClassCheck( PhpParser\Node\Expr\BinaryOp $conditional, - FileSource $source - ) { + FileSource $source, + ): bool|int { if (!$source instanceof StatementsAnalyzer) { return false; } @@ -1467,7 +1470,7 @@ protected static function hasGetClassCheck( && strtolower($conditional->right->name->name) === 'class'; $right_variable_class_const = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch - && $conditional->right->class instanceof PhpParser\Node\Expr\Variable + && !$conditional->right->class instanceof PhpParser\Node\Name && $conditional->right->name instanceof PhpParser\Node\Identifier && strtolower($conditional->right->name->name) === 'class'; @@ -1476,15 +1479,22 @@ protected static function hasGetClassCheck( && $conditional->left->name instanceof PhpParser\Node\Identifier && strtolower($conditional->left->name->name) === 'class'; - $left_type = $source->node_data->getType($conditional->left); + $left_variable_class_const = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch + && !$conditional->left->class instanceof PhpParser\Node\Name + && $conditional->left->name instanceof PhpParser\Node\Identifier + && strtolower($conditional->left->name->name) === 'class'; $left_class_string_t = false; - if ($left_type && $left_type->isSingle()) { - foreach ($left_type->getAtomicTypes() as $type_part) { - if ($type_part instanceof TClassString) { - $left_class_string_t = true; - break; + if (!$left_variable_class_const) { + $left_type = $source->node_data->getType($conditional->left); + + if ($left_type && $left_type->isSingle()) { + foreach ($left_type->getAtomicTypes() as $type_part) { + if ($type_part instanceof TClassString) { + $left_class_string_t = true; + break; + } } } } @@ -1505,29 +1515,26 @@ protected static function hasGetClassCheck( && $conditional->left->name instanceof PhpParser\Node\Identifier && strtolower($conditional->left->name->name) === 'class'; - $left_variable_class_const = $conditional->left instanceof PhpParser\Node\Expr\ClassConstFetch - && $conditional->left->class instanceof PhpParser\Node\Expr\Variable - && $conditional->left->name instanceof PhpParser\Node\Identifier - && strtolower($conditional->left->name->name) === 'class'; - $right_class_string = $conditional->right instanceof PhpParser\Node\Expr\ClassConstFetch && $conditional->right->class instanceof PhpParser\Node\Name && $conditional->right->name instanceof PhpParser\Node\Identifier && strtolower($conditional->right->name->name) === 'class'; - $right_type = $source->node_data->getType($conditional->right); - $right_class_string_t = false; - if ($right_type && $right_type->isSingle()) { - foreach ($right_type->getAtomicTypes() as $type_part) { - if ($type_part instanceof TClassString) { - $right_class_string_t = true; - break; + if (!$right_variable_class_const) { + $right_type = $source->node_data->getType($conditional->right); + + if ($right_type && $right_type->isSingle()) { + foreach ($right_type->getAtomicTypes() as $type_part) { + if ($type_part instanceof TClassString) { + $right_class_string_t = true; + break; + } } } } - + if (($left_get_class || $left_static_class || $left_variable_class_const) && ($right_class_string || $right_class_string_t) ) { @@ -1541,10 +1548,10 @@ protected static function hasGetClassCheck( * @param Greater|GreaterOrEqual|Smaller|SmallerOrEqual $conditional * @return false|int */ - protected static function hasNonEmptyCountEqualityCheck( + private static function hasNonEmptyCountEqualityCheck( PhpParser\Node\Expr\BinaryOp $conditional, - ?int &$min_count - ) { + ?int &$min_count, + ): bool|int { if ($conditional->left instanceof PhpParser\Node\Expr\FuncCall && $conditional->left->name instanceof PhpParser\Node\Name && in_array(strtolower($conditional->left->name->getFirst()), ['count', 'sizeof']) @@ -1568,7 +1575,7 @@ protected static function hasNonEmptyCountEqualityCheck( } // TODO get node type provider here somehow and check literal ints and int ranges - if ($compare_to instanceof PhpParser\Node\Scalar\LNumber + if ($compare_to instanceof PhpParser\Node\Scalar\Int_ && $compare_to->value > (-1 * $comparison_adjustment) ) { $min_count = $compare_to->value + $comparison_adjustment; @@ -1583,10 +1590,10 @@ protected static function hasNonEmptyCountEqualityCheck( * @param Greater|GreaterOrEqual|Smaller|SmallerOrEqual $conditional * @return false|int */ - protected static function hasLessThanCountEqualityCheck( + private static function hasLessThanCountEqualityCheck( PhpParser\Node\Expr\BinaryOp $conditional, - ?int &$max_count - ) { + ?int &$max_count, + ): bool|int { $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall && $conditional->left->name instanceof PhpParser\Node\Name && in_array(strtolower($conditional->left->name->getFirst()), ['count', 'sizeof']) @@ -1598,7 +1605,7 @@ protected static function hasLessThanCountEqualityCheck( if ($left_count && $operator_less_than_or_equal - && $conditional->right instanceof PhpParser\Node\Scalar\LNumber + && $conditional->right instanceof PhpParser\Node\Scalar\Int_ ) { $max_count = $conditional->right->value - ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Smaller ? 1 : 0); @@ -1617,7 +1624,7 @@ protected static function hasLessThanCountEqualityCheck( if ($right_count && $operator_greater_than_or_equal - && $conditional->left instanceof PhpParser\Node\Scalar\LNumber + && $conditional->left instanceof PhpParser\Node\Scalar\Int_ ) { $max_count = $conditional->left->value - ($conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 1 : 0); @@ -1632,16 +1639,16 @@ protected static function hasLessThanCountEqualityCheck( * @param Equal|Identical|NotEqual|NotIdentical $conditional * @return false|int */ - protected static function hasCountEqualityCheck( + private static function hasCountEqualityCheck( PhpParser\Node\Expr\BinaryOp $conditional, - ?int &$count - ) { + ?int &$count, + ): bool|int { $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall && $conditional->left->name instanceof PhpParser\Node\Name && in_array(strtolower($conditional->left->name->getFirst()), ['count', 'sizeof']) && $conditional->left->getArgs(); - if ($left_count && $conditional->right instanceof PhpParser\Node\Scalar\LNumber) { + if ($left_count && $conditional->right instanceof PhpParser\Node\Scalar\Int_) { $count = $conditional->right->value; return self::ASSIGNMENT_TO_RIGHT; @@ -1652,7 +1659,7 @@ protected static function hasCountEqualityCheck( && in_array(strtolower($conditional->right->name->getFirst()), ['count', 'sizeof']) && $conditional->right->getArgs(); - if ($right_count && $conditional->left instanceof PhpParser\Node\Scalar\LNumber) { + if ($right_count && $conditional->left instanceof PhpParser\Node\Scalar\Int_) { $count = $conditional->left->value; return self::ASSIGNMENT_TO_LEFT; @@ -1665,11 +1672,11 @@ protected static function hasCountEqualityCheck( * @param PhpParser\Node\Expr\BinaryOp\Greater|PhpParser\Node\Expr\BinaryOp\GreaterOrEqual $conditional * @return false|int */ - protected static function hasSuperiorNumberCheck( + private static function hasSuperiorNumberCheck( FileSource $source, PhpParser\Node\Expr\BinaryOp $conditional, - ?int &$literal_value_comparison - ) { + ?int &$literal_value_comparison, + ): bool|int { $right_assignment = false; $value_right = null; if ($source instanceof StatementsAnalyzer @@ -1678,13 +1685,13 @@ protected static function hasSuperiorNumberCheck( ) { $right_assignment = true; $value_right = $type->getSingleIntLiteral()->value; - } elseif ($conditional->right instanceof LNumber) { + } elseif ($conditional->right instanceof Int_) { $right_assignment = true; $value_right = $conditional->right->value; - } elseif ($conditional->right instanceof UnaryMinus && $conditional->right->expr instanceof LNumber) { + } elseif ($conditional->right instanceof UnaryMinus && $conditional->right->expr instanceof Int_) { $right_assignment = true; $value_right = -$conditional->right->expr->value; - } elseif ($conditional->right instanceof UnaryPlus && $conditional->right->expr instanceof LNumber) { + } elseif ($conditional->right instanceof UnaryPlus && $conditional->right->expr instanceof Int_) { $right_assignment = true; $value_right = $conditional->right->expr->value; } @@ -1702,13 +1709,13 @@ protected static function hasSuperiorNumberCheck( ) { $left_assignment = true; $value_left = $type->getSingleIntLiteral()->value; - } elseif ($conditional->left instanceof LNumber) { + } elseif ($conditional->left instanceof Int_) { $left_assignment = true; $value_left = $conditional->left->value; - } elseif ($conditional->left instanceof UnaryMinus && $conditional->left->expr instanceof LNumber) { + } elseif ($conditional->left instanceof UnaryMinus && $conditional->left->expr instanceof Int_) { $left_assignment = true; $value_left = -$conditional->left->expr->value; - } elseif ($conditional->left instanceof UnaryPlus && $conditional->left->expr instanceof LNumber) { + } elseif ($conditional->left instanceof UnaryPlus && $conditional->left->expr instanceof Int_) { $left_assignment = true; $value_left = $conditional->left->expr->value; } @@ -1725,11 +1732,11 @@ protected static function hasSuperiorNumberCheck( * @param PhpParser\Node\Expr\BinaryOp\Smaller|PhpParser\Node\Expr\BinaryOp\SmallerOrEqual $conditional * @return false|int */ - protected static function hasInferiorNumberCheck( + private static function hasInferiorNumberCheck( FileSource $source, PhpParser\Node\Expr\BinaryOp $conditional, - ?int &$literal_value_comparison - ) { + ?int &$literal_value_comparison, + ): bool|int { $right_assignment = false; $value_right = null; if ($source instanceof StatementsAnalyzer @@ -1738,13 +1745,13 @@ protected static function hasInferiorNumberCheck( ) { $right_assignment = true; $value_right = $type->getSingleIntLiteral()->value; - } elseif ($conditional->right instanceof LNumber) { + } elseif ($conditional->right instanceof Int_) { $right_assignment = true; $value_right = $conditional->right->value; - } elseif ($conditional->right instanceof UnaryMinus && $conditional->right->expr instanceof LNumber) { + } elseif ($conditional->right instanceof UnaryMinus && $conditional->right->expr instanceof Int_) { $right_assignment = true; $value_right = -$conditional->right->expr->value; - } elseif ($conditional->right instanceof UnaryPlus && $conditional->right->expr instanceof LNumber) { + } elseif ($conditional->right instanceof UnaryPlus && $conditional->right->expr instanceof Int_) { $right_assignment = true; $value_right = $conditional->right->expr->value; } @@ -1762,13 +1769,13 @@ protected static function hasInferiorNumberCheck( ) { $left_assignment = true; $value_left = $type->getSingleIntLiteral()->value; - } elseif ($conditional->left instanceof LNumber) { + } elseif ($conditional->left instanceof Int_) { $left_assignment = true; $value_left = $conditional->left->value; - } elseif ($conditional->left instanceof UnaryMinus && $conditional->left->expr instanceof LNumber) { + } elseif ($conditional->left instanceof UnaryMinus && $conditional->left->expr instanceof Int_) { $left_assignment = true; $value_left = -$conditional->left->expr->value; - } elseif ($conditional->left instanceof UnaryPlus && $conditional->left->expr instanceof LNumber) { + } elseif ($conditional->left instanceof UnaryPlus && $conditional->left->expr instanceof Int_) { $left_assignment = true; $value_left = $conditional->left->expr->value; } @@ -1785,14 +1792,14 @@ protected static function hasInferiorNumberCheck( * @param PhpParser\Node\Expr\BinaryOp\Greater|PhpParser\Node\Expr\BinaryOp\GreaterOrEqual $conditional * @return false|int */ - protected static function hasReconcilableNonEmptyCountEqualityCheck( - PhpParser\Node\Expr\BinaryOp $conditional - ) { + private static function hasReconcilableNonEmptyCountEqualityCheck( + PhpParser\Node\Expr\BinaryOp $conditional, + ): bool|int { $left_count = $conditional->left instanceof PhpParser\Node\Expr\FuncCall && $conditional->left->name instanceof PhpParser\Node\Name && in_array(strtolower($conditional->left->name->getFirst()), ['count', 'sizeof']); - $right_number = $conditional->right instanceof PhpParser\Node\Scalar\LNumber + $right_number = $conditional->right instanceof PhpParser\Node\Scalar\Int_ && $conditional->right->value === ( $conditional instanceof PhpParser\Node\Expr\BinaryOp\Greater ? 0 : 1); @@ -1807,10 +1814,10 @@ protected static function hasReconcilableNonEmptyCountEqualityCheck( * @param Identical|Equal|NotIdentical|NotEqual $conditional * @return false|int */ - protected static function hasTypedValueComparison( + private static function hasTypedValueComparison( PhpParser\Node\Expr\BinaryOp $conditional, - FileSource $source - ) { + FileSource $source, + ): bool|int { if (!$source instanceof StatementsAnalyzer) { return false; } @@ -1841,9 +1848,9 @@ protected static function hasTypedValueComparison( return false; } - protected static function hasIsACheck( + private static function hasIsACheck( PhpParser\Node\Expr\FuncCall $stmt, - StatementsAnalyzer $source + StatementsAnalyzer $source, ): bool { if ($stmt->name instanceof PhpParser\Node\Name && (strtolower($stmt->name->getFirst()) === 'is_a' @@ -1871,44 +1878,24 @@ protected static function hasIsACheck( private static function getIsAssertion(string $function_name): ?Assertion { - switch ($function_name) { - case 'is_string': - return new IsType(new Atomic\TString()); - case 'is_int': - case 'is_integer': - case 'is_long': - return new IsType(new Atomic\TInt()); - case 'is_float': - case 'is_double': - case 'is_real': - return new IsType(new Atomic\TFloat()); - case 'is_scalar': - return new IsType(new Atomic\TScalar()); - case 'is_bool': - return new IsType(new Atomic\TBool()); - case 'is_resource': - return new IsType(new Atomic\TResource()); - case 'is_object': - return new IsType(new Atomic\TObject()); - case 'array_is_list': - return new IsType(Type::getListAtomic(Type::getMixed())); - case 'is_array': - return new IsType(new Atomic\TArray([Type::getArrayKey(), Type::getMixed()])); - case 'is_numeric': - return new IsType(new Atomic\TNumeric()); - case 'is_null': - return new IsType(new Atomic\TNull()); - case 'is_iterable': - return new IsType(new Atomic\TIterable()); - case 'is_countable': - return new IsCountable(); - case 'ctype_digit': - return new IsType(new Atomic\TNumericString); - case 'ctype_lower': - return new IsType(new Atomic\TNonEmptyLowercaseString); - } - - return null; + return match ($function_name) { + 'is_string' => new IsType(new Atomic\TString()), + 'is_int', 'is_integer', 'is_long' => new IsType(new Atomic\TInt()), + 'is_float', 'is_double', 'is_real' => new IsType(new Atomic\TFloat()), + 'is_scalar' => new IsType(new Atomic\TScalar()), + 'is_bool' => new IsType(new Atomic\TBool()), + 'is_resource' => new IsType(new Atomic\TResource()), + 'is_object' => new IsType(new Atomic\TObject()), + 'array_is_list' => new IsType(Type::getListAtomic(Type::getMixed())), + 'is_array' => new IsType(new Atomic\TArray([Type::getArrayKey(), Type::getMixed()])), + 'is_numeric' => new IsType(new Atomic\TNumeric()), + 'is_null' => new IsType(new Atomic\TNull()), + 'is_iterable' => new IsType(new Atomic\TIterable()), + 'is_countable' => new IsCountable(), + 'ctype_digit' => new IsType(new Atomic\TNumericString), + 'ctype_lower' => new IsType(new Atomic\TNonEmptyLowercaseString), + default => null, + }; } /** @@ -1921,7 +1908,7 @@ private static function handleIsTypeCheck( ?string $first_var_name, ?Union $first_var_type, PhpParser\Node\Expr\FuncCall $expr, - bool $negate + bool $negate, ): array { $if_types = []; if ($stmt->name instanceof PhpParser\Node\Name @@ -1957,7 +1944,7 @@ private static function handleIsTypeCheck( return $if_types; } - protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + private static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt): bool { return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'is_callable'; } @@ -1965,7 +1952,7 @@ protected static function hasCallableCheck(PhpParser\Node\Expr\FuncCall $stmt): /** * @return Reconciler::RECONCILIATION_* */ - protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int + private static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int { if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'class_exists' @@ -1991,7 +1978,7 @@ protected static function hasClassExistsCheck(PhpParser\Node\Expr\FuncCall $stmt /** * @return 0|1|2 */ - protected static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int + private static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): int { if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'trait_exists' @@ -2014,22 +2001,22 @@ protected static function hasTraitExistsCheck(PhpParser\Node\Expr\FuncCall $stmt return 0; } - protected static function hasEnumExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + private static function hasEnumExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool { return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'enum_exists'; } - protected static function hasInterfaceExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + private static function hasInterfaceExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool { return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'interface_exists'; } - protected static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + private static function hasFunctionExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool { return $stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'function_exists'; } - protected static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + private static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt): bool { if ($stmt->name instanceof PhpParser\Node\Name && strtolower($stmt->name->getFirst()) === 'in_array' @@ -2047,13 +2034,13 @@ protected static function hasInArrayCheck(PhpParser\Node\Expr\FuncCall $stmt): b return false; } - protected static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + private static function hasNonEmptyCountCheck(PhpParser\Node\Expr\FuncCall $stmt): bool { return $stmt->name instanceof PhpParser\Node\Name && in_array(strtolower($stmt->name->getFirst()), ['count', 'sizeof']); } - protected static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool + private static function hasArrayKeyExistsCheck(PhpParser\Node\Expr\FuncCall $stmt): bool { return $stmt->name instanceof PhpParser\Node\Name && (strtolower($stmt->name->getFirst()) === 'array_key_exists' @@ -2069,7 +2056,7 @@ private static function getNullInequalityAssertions( FileSource $source, ?string $this_class_name, ?Codebase $codebase, - int $null_position + int $null_position, ): array { $if_types = []; @@ -2152,7 +2139,7 @@ private static function getFalseInequalityAssertions( ?Codebase $codebase, int $false_position, bool $cache, - bool $inside_conditional + bool $inside_conditional, ): array { $if_types = []; @@ -2272,7 +2259,7 @@ private static function getTrueInequalityAssertions( ?Codebase $codebase, int $true_position, bool $cache, - bool $inside_conditional + bool $inside_conditional, ): array { $if_types = []; @@ -2424,7 +2411,7 @@ private static function getEmptyInequalityAssertions( ?string $this_class_name, FileSource $source, ?Codebase $codebase, - int $empty_array_position + int $empty_array_position, ): array { $if_types = []; @@ -2500,7 +2487,7 @@ private static function getGettypeInequalityAssertions( PhpParser\Node\Expr\BinaryOp $conditional, ?string $this_class_name, FileSource $source, - int $gettype_position + int $gettype_position, ): array { $if_types = []; @@ -2547,7 +2534,7 @@ private static function getGettypeInequalityAssertions( $if_types[$var_name] = [[new IsNotIdentical(new TObject())]]; } elseif ($var_type === 'resource (closed)') { $if_types[$var_name] = [[new IsNotType(new TClosedResource())]]; - } elseif (strpos($var_type, 'resource (') === 0) { + } elseif (str_starts_with($var_type, 'resource (')) { $if_types[$var_name] = [[new IsNotIdentical(new TResource())]]; } else { $if_types[$var_name] = [[new IsNotType(Atomic::create($var_type))]]; @@ -2566,7 +2553,7 @@ private static function getGetdebugTypeInequalityAssertions( PhpParser\Node\Expr\BinaryOp $conditional, ?string $this_class_name, FileSource $source, - int $get_debug_type_position + int $get_debug_type_position, ): array { $if_types = []; @@ -2605,7 +2592,7 @@ private static function getGetdebugTypeInequalityAssertions( $if_types[$var_name] = [[new IsNotIdentical(new TObject())]]; } elseif ($var_type === 'resource (closed)') { $if_types[$var_name] = [[new IsNotType(new TClosedResource())]]; - } elseif (strpos($var_type, 'resource (') === 0) { + } elseif (str_starts_with($var_type, 'resource (')) { $if_types[$var_name] = [[new IsNotIdentical(new TResource())]]; } else { $if_types[$var_name] = [[new IsNotType(Atomic::create($var_type))]]; @@ -2623,7 +2610,7 @@ private static function getGetclassInequalityAssertions( PhpParser\Node\Expr\BinaryOp $conditional, ?string $this_class_name, StatementsAnalyzer $source, - int $getclass_position + int $getclass_position, ): array { $if_types = []; @@ -2717,7 +2704,7 @@ private static function getTypedValueInequalityAssertions( ?string $this_class_name, StatementsAnalyzer $source, ?Codebase $codebase, - int $typed_value_position + int $typed_value_position, ): array { $if_types = []; @@ -2791,7 +2778,7 @@ private static function getNullEqualityAssertions( ?string $this_class_name, FileSource $source, ?Codebase $codebase, - int $null_position + int $null_position, ): array { $if_types = []; @@ -2873,7 +2860,7 @@ private static function getTrueEqualityAssertions( ?Codebase $codebase, int $true_position, bool $cache, - bool $inside_conditional + bool $inside_conditional, ): array { $if_types = []; @@ -3001,7 +2988,7 @@ private static function getFalseEqualityAssertions( ?Codebase $codebase, int $false_position, bool $cache, - bool $inside_conditional + bool $inside_conditional, ): array { $if_types = []; @@ -3152,7 +3139,7 @@ private static function getEmptyArrayEqualityAssertions( ?string $this_class_name, FileSource $source, ?Codebase $codebase, - int $empty_array_position + int $empty_array_position, ): array { $if_types = []; @@ -3223,7 +3210,7 @@ private static function getGettypeEqualityAssertions( PhpParser\Node\Expr\BinaryOp $conditional, ?string $this_class_name, FileSource $source, - int $gettype_position + int $gettype_position, ): array { $if_types = []; @@ -3260,7 +3247,7 @@ private static function getGettypeEqualityAssertions( $if_types[$var_name] = [[new IsIdentical(new TObject())]]; } elseif ($var_type === 'resource (closed)') { $if_types[$var_name] = [[new IsType(new TClosedResource())]]; - } elseif (strpos($var_type, 'resource (') === 0) { + } elseif (str_starts_with($var_type, 'resource (')) { $if_types[$var_name] = [[new IsIdentical(new TResource())]]; } elseif ($var_type === 'integer') { $if_types[$var_name] = [[new IsType(new Atomic\TInt())]]; @@ -3285,7 +3272,7 @@ private static function getGetdebugtypeEqualityAssertions( PhpParser\Node\Expr\BinaryOp $conditional, ?string $this_class_name, FileSource $source, - int $get_debug_type_position + int $get_debug_type_position, ): array { $if_types = []; @@ -3324,7 +3311,7 @@ private static function getGetdebugtypeEqualityAssertions( $if_types[$var_name] = [[new IsIdentical(new TObject())]]; } elseif ($var_type === 'resource (closed)') { $if_types[$var_name] = [[new IsType(new TClosedResource())]]; - } elseif (strpos($var_type, 'resource (') === 0) { + } elseif (str_starts_with($var_type, 'resource (')) { $if_types[$var_name] = [[new IsIdentical(new TResource())]]; } elseif ($var_type === 'integer') { $if_types[$var_name] = [[new IsType(new Atomic\TInt())]]; @@ -3348,7 +3335,7 @@ private static function getGetclassEqualityAssertions( PhpParser\Node\Expr\BinaryOp $conditional, ?string $this_class_name, StatementsAnalyzer $source, - int $getclass_position + int $getclass_position, ): array { $if_types = []; @@ -3444,7 +3431,7 @@ private static function getTypedValueEqualityAssertions( ?string $this_class_name, StatementsAnalyzer $source, ?Codebase $codebase, - int $typed_value_position + int $typed_value_position, ): array { $if_types = []; @@ -3549,7 +3536,7 @@ private static function getIsaAssertions( PhpParser\Node\Expr\FuncCall $expr, StatementsAnalyzer $source, ?string $this_class_name, - ?string $first_var_name + ?string $first_var_name, ): array { $if_types = []; @@ -3652,7 +3639,7 @@ private static function getIsaAssertions( private static function getInarrayAssertions( PhpParser\Node\Expr\FuncCall $expr, StatementsAnalyzer $source, - ?string $first_var_name + ?string $first_var_name, ): array { $if_types = []; @@ -3662,9 +3649,6 @@ private static function getInarrayAssertions( && !$expr->getArgs()[0]->value instanceof PhpParser\Node\Expr\ClassConstFetch ) { foreach ($second_arg_type->getAtomicTypes() as $atomic_type) { - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } if ($atomic_type instanceof TArray || $atomic_type instanceof TKeyedArray ) { @@ -3731,7 +3715,7 @@ private static function getArrayKeyExistsAssertions( ?Union $first_var_type, ?string $first_var_name, FileSource $source, - ?string $this_class_name + ?string $this_class_name, ): array { $if_types = []; @@ -3748,10 +3732,6 @@ private static function getArrayKeyExistsAssertions( && ($second_var_type = $source->node_data->getType($expr->getArgs()[1]->value)) ) { foreach ($second_var_type->getAtomicTypes() as $atomic_type) { - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } - if ($atomic_type instanceof TArray || $atomic_type instanceof TKeyedArray ) { @@ -3872,7 +3852,7 @@ private static function getArrayKeyExistsAssertions( private static function getGreaterAssertions( PhpParser\Node\Expr $conditional, FileSource $source, - ?string $this_class_name + ?string $this_class_name, ): array { $if_types = []; @@ -3985,7 +3965,7 @@ private static function getGreaterAssertions( private static function getSmallerAssertions( PhpParser\Node\Expr $conditional, FileSource $source, - ?string $this_class_name + ?string $this_class_name, ): array { $if_types = []; $min_count = null; @@ -4095,7 +4075,7 @@ private static function getAndCheckInstanceofAssertions( ?Codebase $codebase, FileSource $source, ?string $this_class_name, - bool $inside_negation + bool $inside_negation, ): array { $if_types = []; @@ -4177,7 +4157,7 @@ private static function handleParadoxicalAssertions( ?string $this_class_name, Union $other_type, Codebase $codebase, - PhpParser\Node\Expr\BinaryOp $conditional + PhpParser\Node\Expr\BinaryOp $conditional, ): void { $parent_source = $source->getSource(); @@ -4230,7 +4210,7 @@ public static function isPropertyImmutableOnArgument( string $property, NodeDataProvider $node_provider, ClassLikeStorageProvider $class_provider, - PhpParser\Node\Expr\Variable $arg_expr + PhpParser\Node\Expr\Variable $arg_expr, ): ?string { $type = $node_provider->getType($arg_expr); /** @var string $name */ diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php index 9347390fa28..ec07e8c0715 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/ArrayAssignmentAnalyzer.php @@ -1,5 +1,7 @@ value); } elseif ($current_dim && ($key_type = $statements_analyzer->node_data->getType($current_dim)) @@ -289,7 +289,7 @@ private static function updateTypeWithKeyValues( Codebase $codebase, Union $child_stmt_type, Union $current_type, - array $key_values + array $key_values, ): Union { $has_matching_objectlike_property = false; $has_matching_string = false; @@ -297,9 +297,6 @@ private static function updateTypeWithKeyValues( $changed = false; $types = []; foreach ($child_stmt_type->getAtomicTypes() as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } $old_type = $type; if ($type instanceof TTemplateParam) { $type = $type->replaceAs(self::updateTypeWithKeyValues( @@ -390,7 +387,7 @@ private static function taintArrayAssignment( Union &$stmt_type, Union $child_stmt_type, ?string $var_var_id, - array $key_values + array $key_values, ): void { if ($statements_analyzer->data_flow_graph && ($statements_analyzer->data_flow_graph instanceof VariableUseGraph @@ -455,7 +452,7 @@ private static function updateArrayAssignmentChildType( Union $value_type, Union $root_type, bool $offset_already_existed, - ?string $parent_var_id + ?string $parent_var_id, ): Union { $templated_assignment = false; @@ -475,8 +472,6 @@ private static function updateArrayAssignmentChildType( if (($key_type_type instanceof TIntRange && $key_type_type->dependent_list_key === $parent_var_id - ) || ($key_type_type instanceof TDependentListKey - && $key_type_type->var_id === $parent_var_id )) { $offset_already_existed = true; } @@ -511,7 +506,7 @@ private static function updateArrayAssignmentChildType( } if ($parent_var_id && ($parent_type = $context->vars_in_scope[$parent_var_id] ?? null)) { - if ($offset_already_existed && $parent_type->hasList() && strpos($parent_var_id, '[') === false) { + if ($offset_already_existed && $parent_type->hasList() && !str_contains($parent_var_id, '[')) { $array_atomic_type_list = $value_type; } elseif ($parent_type->hasClassStringMap() && $key_type @@ -580,9 +575,7 @@ private static function updateArrayAssignmentChildType( if (isset($atomic_root_types['array'])) { $atomic_root_type_array = $atomic_root_types['array']; - if ($atomic_root_type_array instanceof TList) { - $atomic_root_type_array = $atomic_root_type_array->getKeyedArray(); - } + if ($array_atomic_type_class_string) { $array_atomic_type = new TNonEmptyArray([ @@ -704,9 +697,7 @@ private static function updateArrayAssignmentChildType( if (isset($atomic_root_types['array'])) { $atomic_root_type_array = $atomic_root_types['array']; - if ($atomic_root_type_array instanceof TList) { - $atomic_root_type_array = $atomic_root_type_array->getKeyedArray(); - } + if ($atomic_root_type_array instanceof TNonEmptyArray && $atomic_root_type_array->count !== null @@ -757,7 +748,7 @@ private static function analyzeNestedArrayAssignment( Union &$root_type, Union &$current_type, ?PhpParser\Node\Expr &$current_dim, - bool &$offset_already_existed + bool &$offset_already_existed, ): void { $var_id_additions = []; @@ -1027,7 +1018,7 @@ private static function analyzeNestedArrayAssignment( */ private static function getDimKeyValues( StatementsAnalyzer $statements_analyzer, - PhpParser\Node\Expr $dim + PhpParser\Node\Expr $dim, ): array { $key_values = []; @@ -1036,7 +1027,7 @@ private static function getDimKeyValues( if ($value_type instanceof TLiteralString) { $key_values[] = $value_type; } - } elseif ($dim instanceof PhpParser\Node\Scalar\LNumber) { + } elseif ($dim instanceof PhpParser\Node\Scalar\Int_) { $key_values[] = new TLiteralInt($dim->value); } else { $key_type = $statements_analyzer->node_data->getType($dim); @@ -1068,7 +1059,7 @@ private static function getDimKeyValues( private static function getArrayAssignmentOffsetType( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\ArrayDimFetch $child_stmt, - Union $child_stmt_dim_type + Union $child_stmt_dim_type, ): array { if ($child_stmt->dim instanceof PhpParser\Node\Scalar\String_ || (($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch @@ -1094,12 +1085,12 @@ private static function getArrayAssignmentOffsetType( return [$offset_type, $var_id_addition, true]; } - if ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber + if ($child_stmt->dim instanceof PhpParser\Node\Scalar\Int_ || (($child_stmt->dim instanceof PhpParser\Node\Expr\ConstFetch || $child_stmt->dim instanceof PhpParser\Node\Expr\ClassConstFetch) && $child_stmt_dim_type->isSingleIntLiteral()) ) { - if ($child_stmt->dim instanceof PhpParser\Node\Scalar\LNumber) { + if ($child_stmt->dim instanceof PhpParser\Node\Scalar\Int_) { $offset_type = new TLiteralInt($child_stmt->dim->value); } else { $offset_type = $child_stmt_dim_type->getSingleIntLiteral(); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php index 828c9181b7f..2f55918ffec 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/AssignedProperty.php @@ -1,5 +1,7 @@ property_type = $property_type; - $this->id = $id; - $this->assignment_type = $assignment_type; + public function __construct(public Union $property_type, public string $id, public Union $assignment_type) + { } } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php index b8c236e087f..dcc21be9a17 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/InstancePropertyAssignmentAnalyzer.php @@ -1,11 +1,13 @@ getCodebase(); - if ($stmt instanceof PropertyProperty) { + if ($stmt instanceof PropertyItem) { if (!$context->self || !$stmt->default) { return; } @@ -120,7 +122,7 @@ public static function analyze( $statements_analyzer, $context, ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { // do nothing } @@ -360,7 +362,7 @@ public static function trackPropertyImpurity( string $property_id, PropertyStorage $property_storage, ClassLikeStorage $declaring_class_storage, - Context $context + Context $context, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -412,7 +414,7 @@ public static function trackPropertyImpurity( public static function analyzeStatement( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Stmt\Property $stmt, - Context $context + Context $context, ): void { foreach ($stmt->props as $prop) { if ($prop->default) { @@ -448,7 +450,7 @@ private static function taintProperty( string $property_id, ClassLikeStorage $class_storage, Union &$assignment_value_type, - Context $context + Context $context, ): void { if (!$statements_analyzer->data_flow_graph) { return; @@ -565,7 +567,7 @@ public static function taintUnspecializedProperty( ClassLikeStorage $class_storage, Union $assignment_value_type, Context $context, - ?string $var_property_id + ?string $var_property_id, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -662,7 +664,7 @@ private static function analyzeRegularAssignment( Codebase $codebase, Union $assignment_value_type, string $prop_name, - ?string &$var_id + ?string &$var_id, ): array { $was_inside_general_use = $context->inside_general_use; $context->inside_general_use = true; @@ -882,7 +884,7 @@ private static function analyzeAtomicAssignment( Union $assignment_value_type, ?string $lhs_var_id, bool &$has_valid_assignment_type, - bool &$has_regular_setter + bool &$has_regular_setter, ): ?AssignedProperty { if ($lhs_type_part instanceof TNull) { return null; @@ -1430,7 +1432,7 @@ private static function handlePropertyRenames( string $declaring_property_class, string $prop_name, PropertyFetch $stmt, - string $file_path + string $file_path, ): void { if (!$codebase->properties_to_rename) { return; @@ -1460,7 +1462,7 @@ public static function getExpandedPropertyType( Codebase $codebase, string $fq_class_name, string $property_name, - ClassLikeStorage $storage + ClassLikeStorage $storage, ): ?Union { $property_class_name = $codebase->properties->getDeclaringClassForProperty( $fq_class_name . '::$' . $property_name, @@ -1528,7 +1530,7 @@ private static function analyzeSetCall( StatementsAnalyzer $statements_analyzer, PropertyFetch $stmt, string $prop_name, - Expr $assignment_value + Expr $assignment_value, ): void { if ($var_id) { $context->removeVarFromConflictingClauses( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php index cff735cd273..990aed4dfdb 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Assignment/StaticPropertyAssignmentAnalyzer.php @@ -1,5 +1,7 @@ getFQCLN(), @@ -256,7 +258,7 @@ public static function analyze( $context->vars_in_scope[$var_id] = $comment_type ?? Type::getMixed(); } - return false; + return null; } $context->inside_general_use = $was_inside_general_use; @@ -400,7 +402,7 @@ public static function analyze( if (!$assign_var instanceof PhpParser\Node\Expr\PropertyFetch && !strpos($root_var_id ?? '', '->') && !$comment_type - && strpos($var_id ?? '', '$_') !== 0 + && !str_starts_with($var_id ?? '', '$_') ) { $origin_locations = []; @@ -478,7 +480,7 @@ public static function analyze( ), $statements_analyzer->getSuppressedIssues(), )) { - return false; + return null; } if (isset($context->protected_var_ids[$var_id]) @@ -507,7 +509,7 @@ public static function analyze( $removed_taints, ) === false ) { - return false; + return null; } if ($var_id && isset($context->vars_in_scope[$var_id])) { @@ -621,7 +623,7 @@ private static function analyzeAssignment( ?Doc $doc_comment, ?string $extended_var_id, array $var_comments, - array $removed_taints + array $removed_taints, ): ?bool { if ($assign_var instanceof PhpParser\Node\Expr\Variable) { self::analyzeAssignmentToVariable( @@ -699,7 +701,7 @@ public static function assignTypeFromVarDocblock( ?Union &$comment_type = null, ?DocblockTypeLocation &$comment_type_location = null, array $not_ignored_docblock_var_ids = [], - bool $by_ref = false + bool $by_ref = false, ): void { if (!$var_comment->type) { return; @@ -813,7 +815,7 @@ private static function taintAssignment( string $var_id, CodeLocation $var_location, array $removed_taints, - array $added_taints + array $added_taints, ): void { $parent_nodes = $type->parent_nodes; @@ -837,7 +839,7 @@ private static function taintAssignment( public static function analyzeAssignmentOperation( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\AssignOp $stmt, - Context $context + Context $context, ): bool { if ($stmt instanceof PhpParser\Node\Expr\AssignOp\BitwiseAnd) { $operation = new VirtualBitwiseAnd($stmt->var, $stmt->expr, $stmt->getAttributes()); @@ -896,9 +898,10 @@ public static function analyzeAssignmentOperation( public static function analyzeAssignmentRef( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\AssignRef $stmt, - Context $context + Context $context, + ?PhpParser\Node\Stmt $from_stmt, ): bool { - ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context, false, null, false, null, true); + ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context, false, null, null, null, true); $lhs_var_id = ExpressionIdentifier::getExtendedVarId( $stmt->var, @@ -912,7 +915,7 @@ public static function analyzeAssignmentRef( $statements_analyzer, ); - $doc_comment = $stmt->getDocComment(); + $doc_comment = $stmt->getDocComment() ?? $from_stmt?->getDocComment(); if ($doc_comment) { try { $var_comments = CommentAnalyzer::getTypeFromComment( @@ -962,7 +965,7 @@ public static function analyzeAssignmentRef( // Remove old reference parent node so previously referenced variable usage doesn't count as reference usage $old_type = $context->vars_in_scope[$lhs_var_id]; foreach ($old_type->parent_nodes as $old_parent_node_id => $_) { - if (strpos($old_parent_node_id, "$lhs_var_id-") === 0) { + if (str_starts_with($old_parent_node_id, "$lhs_var_id-")) { unset($old_type->parent_nodes[$old_parent_node_id]); } } @@ -975,12 +978,12 @@ public static function analyzeAssignmentRef( $context->hasVariable($lhs_var_id); $context->references_in_scope[$lhs_var_id] = $rhs_var_id; $context->referenced_counts[$rhs_var_id] = ($context->referenced_counts[$rhs_var_id] ?? 0) + 1; - if (strpos($rhs_var_id, '[') !== false) { + if (str_contains($rhs_var_id, '[')) { // Reference to array item, we always consider array items to be an external scope for references // TODO handle differently so it's detected as unused if the array is unused? $context->references_to_external_scope[$lhs_var_id] = true; } - if (strpos($rhs_var_id, '->') !== false) { + if (str_contains($rhs_var_id, '->')) { IssueBuffer::maybeAdd( new UnsupportedPropertyReferenceUsage( new CodeLocation($statements_analyzer->getSource(), $stmt), @@ -991,7 +994,7 @@ public static function analyzeAssignmentRef( // TODO handle differently so it's detected as unused if the object is unused? $context->references_to_external_scope[$lhs_var_id] = true; } - if (strpos($rhs_var_id, '::') !== false) { + if (str_contains($rhs_var_id, '::')) { IssueBuffer::maybeAdd( new UnsupportedPropertyReferenceUsage( new CodeLocation($statements_analyzer->getSource(), $stmt), @@ -1033,7 +1036,7 @@ public static function assignByRefParam( Union $by_ref_out_type, Context $context, bool $constrain_type = true, - bool $prevent_null = false + bool $prevent_null = false, ): void { if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch && $stmt->name instanceof PhpParser\Node\Identifier) { $prop_name = $stmt->name->name; @@ -1169,7 +1172,7 @@ private static function analyzeDestructuringAssignment( ?PhpParser\Comment\Doc $doc_comment, ?string $extended_var_id, array $var_comments, - array $removed_taints + array $removed_taints, ): void { if (!$assign_value_type->hasArray() && !$assign_value_type->isMixed() @@ -1235,9 +1238,6 @@ private static function analyzeDestructuringAssignment( $has_null = false; foreach ($assign_value_type->getAtomicTypes() as $assign_value_atomic_type) { - if ($assign_value_atomic_type instanceof TList) { - $assign_value_atomic_type = $assign_value_atomic_type->getKeyedArray(); - } if ($assign_value_atomic_type instanceof TKeyedArray && !$assign_var_item->key ) { @@ -1380,7 +1380,7 @@ private static function analyzeDestructuringAssignment( $already_in_scope = isset($context->vars_in_scope[$list_var_id]); - if (strpos($list_var_id, '-') === false && strpos($list_var_id, '[') === false) { + if (!str_contains($list_var_id, '-') && !str_contains($list_var_id, '[')) { $location = new CodeLocation($statements_analyzer, $var); if (!$statements_analyzer->hasVariable($list_var_id)) { @@ -1420,7 +1420,7 @@ private static function analyzeDestructuringAssignment( $can_be_empty = !$assign_value_atomic_type instanceof TNonEmptyArray; } elseif ($assign_value_atomic_type instanceof TKeyedArray) { if (($assign_var_item->key instanceof PhpParser\Node\Scalar\String_ - || $assign_var_item->key instanceof PhpParser\Node\Scalar\LNumber) + || $assign_var_item->key instanceof PhpParser\Node\Scalar\Int_) && isset($assign_value_atomic_type->properties[$assign_var_item->key->value]) ) { $new_assign_type = @@ -1601,7 +1601,7 @@ private static function analyzePropertyAssignment( Context $context, ?PhpParser\Node\Expr $assign_value, Union $assign_value_type, - ?string $var_id + ?string $var_id, ): void { if (!$assign_var->name instanceof PhpParser\Node\Identifier) { $was_inside_general_use = $context->inside_general_use; @@ -1706,7 +1706,7 @@ private static function analyzeAssignmentToVariable( ?PhpParser\Node\Expr $assign_value, Union $assign_value_type, ?string $var_id, - Context $context + Context $context, ): void { if (is_string($assign_var->name)) { if ($var_id) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php index 982cb4d78c7..9472ce6edb0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/AndAnalyzer.php @@ -1,5 +1,7 @@ if_body_context && !$context->inside_negation) { $if_body_context = $context->if_body_context; $context->vars_in_scope = $right_context->vars_in_scope; - $if_body_context->vars_in_scope = array_merge( - $if_body_context->vars_in_scope, - $context->vars_in_scope, - ); + $if_body_context->vars_in_scope = [ + ...$if_body_context->vars_in_scope, + ...$context->vars_in_scope, + ]; - $if_body_context->cond_referenced_var_ids = array_merge( - $if_body_context->cond_referenced_var_ids, - $context->cond_referenced_var_ids, - ); + $if_body_context->cond_referenced_var_ids = [ + ...$if_body_context->cond_referenced_var_ids, + ...$context->cond_referenced_var_ids, + ]; - $if_body_context->assigned_var_ids = array_merge( - $if_body_context->assigned_var_ids, - $context->assigned_var_ids, - ); + $if_body_context->assigned_var_ids = [ + ...$if_body_context->assigned_var_ids, + ...$context->assigned_var_ids, + ]; $if_body_context->reconciled_expression_clauses = [ ...$if_body_context->reconciled_expression_clauses, @@ -210,10 +212,10 @@ public static function analyze( ), ]; - $if_body_context->vars_possibly_in_scope = array_merge( - $if_body_context->vars_possibly_in_scope, - $context->vars_possibly_in_scope, - ); + $if_body_context->vars_possibly_in_scope = [ + ...$if_body_context->vars_possibly_in_scope, + ...$context->vars_possibly_in_scope, + ]; $if_body_context->updateChecks($context); } else { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php index 7fc47efd872..10d14b45f18 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ArithmeticOpAnalyzer.php @@ -1,5 +1,7 @@ getCodebase() : null; @@ -277,10 +279,7 @@ public static function analyze( } } - /** - * @param int|float $result - */ - private static function getNumericalType($result): Union + private static function getNumericalType(int|float $result): Union { if (is_int($result)) { return Type::getInt(false, $result); @@ -309,7 +308,7 @@ private static function analyzeOperands( bool &$has_valid_left_operand, bool &$has_valid_right_operand, bool &$has_string_increment, - ?Union &$result_type = null + ?Union &$result_type = null, ): ?Union { if (($left_type_part instanceof TLiteralInt || $left_type_part instanceof TLiteralFloat) && ($right_type_part instanceof TLiteralInt || $right_type_part instanceof TLiteralFloat) @@ -323,7 +322,7 @@ private static function analyzeOperands( // get_class is fine here because both classes are final. if ($statements_source !== null && $config->strict_binary_operands - && get_class($left_type_part) !== get_class($right_type_part) + && $left_type_part::class !== $right_type_part::class ) { IssueBuffer::maybeAdd( new InvalidOperand( @@ -510,15 +509,7 @@ private static function analyzeOperands( || $right_type_part instanceof TArray || $left_type_part instanceof TKeyedArray || $right_type_part instanceof TKeyedArray - || $left_type_part instanceof TList - || $right_type_part instanceof TList ) { - if ($left_type_part instanceof TList) { - $left_type_part = $left_type_part->getKeyedArray(); - } - if ($right_type_part instanceof TList) { - $right_type_part = $right_type_part->getKeyedArray(); - } if ((!$right_type_part instanceof TArray && !$right_type_part instanceof TKeyedArray) || (!$left_type_part instanceof TArray @@ -634,6 +625,11 @@ private static function analyzeOperands( return null; } } + /** + * @var Atomic $left_type_part + * @var Atomic $right_type_part + * // Todo remove this hint reset after fixing #10267 + */ if (($left_type_part instanceof TNamedObject && strtolower($left_type_part->value) === 'gmp') || ($right_type_part instanceof TNamedObject && strtolower($right_type_part->value) === 'gmp') @@ -826,6 +822,28 @@ private static function analyzeOperands( $result_type = Type::getInt(); } } + } elseif ($parent instanceof VirtualPlus || $parent instanceof VirtualMinus) { + $sum = $parent instanceof VirtualPlus ? 1 : -1; + if ($context && $context->inside_loop && $left_type_part instanceof TLiteralInt) { + if ($parent instanceof VirtualPlus) { + $new_type = new TIntRange($left_type_part->value + $sum, null); + } else { + $new_type = new TIntRange(null, $left_type_part->value + $sum); + } + } elseif ($left_type_part instanceof TLiteralInt) { + $new_type = new TLiteralInt($left_type_part->value + $sum); + } elseif ($left_type_part instanceof TIntRange) { + $start = $left_type_part->min_bound === null ? null : $left_type_part->min_bound + $sum; + $end = $left_type_part->max_bound === null ? null : $left_type_part->max_bound + $sum; + $new_type = new TIntRange($start, $end); + } else { + $new_type = new TInt(); + } + + $result_type = Type::combineUnionTypes( + new Union([$new_type], ['from_calculation' => true]), + $result_type, + ); } else { $result_type = Type::combineUnionTypes( $always_positive ? Type::getIntRange(1, null) : Type::getInt(true), @@ -925,15 +943,11 @@ private static function analyzeOperands( return null; } - /** - * @param float|int $operand1 - * @param float|int $operand2 - */ public static function arithmeticOperation( PhpParser\Node $operation, - $operand1, - $operand2, - bool $allow_float_result + float|int $operand1, + float|int $operand2, + bool $allow_float_result, ): ?Union { if ($operation instanceof PhpParser\Node\Expr\BinaryOp\Plus) { $result = $operand1 + $operand2; @@ -981,7 +995,7 @@ private static function analyzeOperandsBetweenIntRange( PhpParser\Node $parent, ?Union &$result_type, TIntRange $left_type_part, - TIntRange $right_type_part + TIntRange $right_type_part, ): void { if ($parent instanceof PhpParser\Node\Expr\BinaryOp\Div) { //can't assume an int range will stay int after division @@ -1083,7 +1097,7 @@ private static function analyzeOperandsBetweenIntRangeAndInt( PhpParser\Node $parent, ?Union &$result_type, Atomic $left_type_part, - Atomic $right_type_part + Atomic $right_type_part, ): void { if (!$left_type_part instanceof TIntRange) { $left_type_part = TIntRange::convertToIntRange($left_type_part); @@ -1099,7 +1113,7 @@ private static function analyzeMulBetweenIntRange( PhpParser\Node\Expr\BinaryOp\Mul $parent, ?Union &$result_type, TIntRange $left_type_part, - TIntRange $right_type_part + TIntRange $right_type_part, ): void { //Mul is a special case because of double negatives. We can only infer when we know both signs strictly if ($right_type_part->min_bound !== null @@ -1275,7 +1289,7 @@ private static function analyzeMulBetweenIntRange( private static function analyzePowBetweenIntRange( ?Union &$result_type, TIntRange $left_type_part, - TIntRange $right_type_part + TIntRange $right_type_part, ): void { //If Pow first operand is negative, the result could be positive or negative, else it will be positive //If Pow second operand is negative, the result will be float, if it's 0, it will be 1/-1, else positive @@ -1348,7 +1362,7 @@ private static function analyzePowBetweenIntRange( private static function analyzeModBetweenIntRange( ?Union &$result_type, TIntRange $left_type_part, - TIntRange $right_type_part + TIntRange $right_type_part, ): void { //result of Mod is not directly dependant on the bounds of the range if ($right_type_part->min_bound !== null && $right_type_part->min_bound === $right_type_part->max_bound) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php index 72194f55224..e50f0bb55e2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/CoalesceAnalyzer.php @@ -1,5 +1,7 @@ left; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php index a870acd6a70..84f75075ed7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/ConcatAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -179,9 +181,6 @@ public static function analyze( } if ($literal_concat) { - // Bypass opcache bug: https://github.com/php/php-src/issues/10635 - (function (int $_): void { - })($combinations); if (count($result_type_parts) === 0) { throw new AssertionError("The number of parts cannot be 0!"); } @@ -326,7 +325,7 @@ private static function analyzeOperand( PhpParser\Node\Expr $operand, Union $operand_type, string $side, - Context $context + Context $context, ): void { $codebase = $statements_analyzer->getCodebase(); $config = Config::getInstance(); @@ -440,7 +439,7 @@ private static function analyzeOperand( )) { try { $storage = $codebase->methods->getStorage($to_string_method_id); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { continue; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php index 63bff0ed888..9b3b804d7b2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/NonComparisonOpAnalyzer.php @@ -1,5 +1,7 @@ node_data->getType($stmt->left); $stmt_right_type = $statements_analyzer->node_data->getType($stmt->right); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php index ed716f66e91..614b3993ca1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php @@ -1,5 +1,7 @@ left instanceof PhpParser\Node\Expr\BinaryOp\BooleanOr) { $post_leaving_if_context = clone $context; } - } catch (ScopeAnalysisException $e) { + } catch (ScopeAnalysisException) { return false; } } else { @@ -130,10 +132,16 @@ public static function analyze( } $left_referenced_var_ids = $left_context->cond_referenced_var_ids; - $left_context->cond_referenced_var_ids = array_merge($pre_referenced_var_ids, $left_referenced_var_ids); + $left_context->cond_referenced_var_ids = [ + ...$pre_referenced_var_ids, + ...$left_referenced_var_ids, + ]; $left_assigned_var_ids = array_diff_key($left_context->assigned_var_ids, $pre_assigned_var_ids); - $left_context->assigned_var_ids = array_merge($pre_assigned_var_ids, $left_context->assigned_var_ids); + $left_context->assigned_var_ids = [ + ...$pre_assigned_var_ids, + ...$left_context->assigned_var_ids, + ]; $left_referenced_var_ids = array_diff_key($left_referenced_var_ids, $left_assigned_var_ids); } @@ -151,7 +159,7 @@ public static function analyze( try { $negated_left_clauses = Algebra::negateFormula($left_clauses); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { try { $negated_left_clauses = FormulaGenerator::getFormula( $left_cond_id, @@ -162,7 +170,7 @@ public static function analyze( $codebase, false, ); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { return false; } } @@ -352,15 +360,12 @@ public static function analyze( $context->updateChecks($right_context); } - $context->cond_referenced_var_ids = array_merge( - $right_context->cond_referenced_var_ids, - $context->cond_referenced_var_ids, - ); + $context->cond_referenced_var_ids = [ + ...$right_context->cond_referenced_var_ids, + ...$context->cond_referenced_var_ids, + ]; - $context->assigned_var_ids = array_merge( - $context->assigned_var_ids, - $right_context->assigned_var_ids, - ); + $context->assigned_var_ids = [...$context->assigned_var_ids, ...$right_context->assigned_var_ids]; if ($context->if_body_context) { $if_body_context = $context->if_body_context; @@ -381,23 +386,23 @@ public static function analyze( } } - $if_body_context->cond_referenced_var_ids = array_merge( - $context->cond_referenced_var_ids, - $if_body_context->cond_referenced_var_ids, - ); + $if_body_context->cond_referenced_var_ids = [ + ...$context->cond_referenced_var_ids, + ...$if_body_context->cond_referenced_var_ids, + ]; - $if_body_context->assigned_var_ids = array_merge( - $context->assigned_var_ids, - $if_body_context->assigned_var_ids, - ); + $if_body_context->assigned_var_ids = [ + ...$context->assigned_var_ids, + ...$if_body_context->assigned_var_ids, + ]; $if_body_context->updateChecks($context); } - $context->vars_possibly_in_scope = array_merge( - $right_context->vars_possibly_in_scope, - $context->vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$right_context->vars_possibly_in_scope, + ...$context->vars_possibly_in_scope, + ]; return true; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php index 2b7b8b607ae..9a782307f13 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php @@ -1,5 +1,7 @@ 100) { $statements_analyzer->node_data->setType($stmt, Type::getString()); @@ -373,7 +375,7 @@ public static function addDataFlow( PhpParser\Node\Expr $stmt, PhpParser\Node\Expr $left, PhpParser\Node\Expr $right, - string $type = 'binaryop' + string $type = 'binaryop', ): void { if ($stmt->getLine() === -1) { throw new UnexpectedValueException('bad'); @@ -455,7 +457,7 @@ private static function checkForImpureEqualityComparison( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\BinaryOp\Equal $stmt, Union $stmt_left_type, - Union $stmt_right_type + Union $stmt_right_type, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -469,7 +471,7 @@ private static function checkForImpureEqualityComparison( '__tostring', ), ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { continue; } @@ -503,7 +505,7 @@ private static function checkForImpureEqualityComparison( '__tostring', ), ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { continue; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php index e9b16763e37..23d0256c389 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BitwiseNotAnalyzer.php @@ -1,5 +1,7 @@ expr, $context) === false) { return false; @@ -104,7 +106,7 @@ public static function analyze( private static function addDataFlow( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $stmt, - PhpParser\Node\Expr $value + PhpParser\Node\Expr $value, ): void { $result_type = $statements_analyzer->node_data->getType($stmt); if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph && $result_type) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php index e6b29ff736c..c4a61fc136b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -243,7 +246,7 @@ private static function checkFunctionLikeTypeMatches( ?array $class_generic_params, ?TemplateResult $template_result, bool $specialize_taint, - bool $in_call_map + bool $in_call_map, ): ?bool { if (!$function_param->type) { if (!$codebase->infer_types_from_usage && !$statements_analyzer->data_flow_graph) { @@ -334,10 +337,6 @@ private static function checkFunctionLikeTypeMatches( $arg_type_param = null; foreach ($arg_value_type->getAtomicTypes() as $arg_atomic_type) { - if ($arg_atomic_type instanceof TList) { - $arg_atomic_type = $arg_atomic_type->getKeyedArray(); - } - if ($arg_atomic_type instanceof TArray || $arg_atomic_type instanceof TKeyedArray ) { @@ -679,7 +678,7 @@ public static function verifyType( ?Atomic $unpacked_atomic_array, bool $specialize_taint, bool $in_call_map, - CodeLocation $function_call_location + CodeLocation $function_call_location, ): ?bool { $codebase = $statements_analyzer->getCodebase(); @@ -689,7 +688,7 @@ public static function verifyType( && !$param_type->from_docblock && !$param_type->had_template && $method_id - && strpos($method_id->method_name, '__') !== 0 + && !str_starts_with($method_id->method_name, '__') ) { $declaring_method_id = $codebase->methods->getDeclaringMethodId($method_id); @@ -858,9 +857,6 @@ public static function verifyType( if ($candidate_callable && $candidate_callable !== $atomic_type) { // if we had an array callable, mark it as used now, since it's not possible later $potential_method_id = null; - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } if ($atomic_type instanceof TKeyedArray) { $potential_method_id = CallableTypeComparator::getCallableMethodIdFromTKeyedArray( @@ -965,10 +961,6 @@ public static function verifyType( : null; foreach ($input_type->getAtomicTypes() as $input_type_part) { - if ($input_type_part instanceof TList) { - $input_type_part = $input_type_part->getKeyedArray(); - } - if ($input_type_part instanceof TKeyedArray) { // If the param accept an array, we don't report arrays as wrong callbacks. if (null !== $param_type_without_callable && UnionTypeComparator::isContainedBy( @@ -1315,7 +1307,7 @@ private static function verifyCallableInContext( CodeLocation $arg_location, Context $context, Codebase $codebase, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): ?bool { $method_identifier = $cased_method_id !== null ? ' of ' . $cased_method_id : ''; @@ -1423,7 +1415,7 @@ private static function verifyCallableInContext( return false; } } - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { // do nothing } } @@ -1439,7 +1431,7 @@ private static function verifyExplicitParam( Union $param_type, CodeLocation $arg_location, PhpParser\Node\Expr $input_expr, - Context $context + Context $context, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -1509,7 +1501,7 @@ private static function verifyExplicitParam( ); foreach ($function_ids as $function_id) { - if (strpos($function_id, '::') !== false) { + if (str_contains($function_id, '::')) { if ($function_id[0] === '$') { $function_id = substr($function_id, 1); } @@ -1625,12 +1617,22 @@ private static function coerceValueAfterGatekeeperArgument( ?Union $signature_param_type, Context $context, bool $unpack, - ?Atomic $unpacked_atomic_array + ?Atomic $unpacked_atomic_array, ): void { if ($param_type->hasMixed()) { return; } + $var_id = ExpressionIdentifier::getVarId( + $input_expr, + $statements_analyzer->getFQCLN(), + $statements_analyzer, + ); + + if (!$var_id) { + return; + } + if (!$input_type_changed && $param_type->from_docblock && !$input_type->hasMixed()) { $types = $input_type->getAtomicTypes(); foreach ($param_type->getAtomicTypes() as $param_atomic_type) { @@ -1669,74 +1671,67 @@ private static function coerceValueAfterGatekeeperArgument( $input_type = new Union($types); } - $var_id = ExpressionIdentifier::getVarId( - $input_expr, - $statements_analyzer->getFQCLN(), - $statements_analyzer, - ); - - if ($var_id) { - $was_cloned = false; - if ($input_type->isNullable() && !$param_type->isNullable()) { - $input_type = $input_type->getBuilder(); - $was_cloned = true; - $input_type->removeType('null'); - $input_type = $input_type->freeze(); - } + $was_cloned = false; - if ($input_type->getId() === $param_type->getId()) { - if ($input_type->from_docblock) { - $input_type = $input_type->setFromDocblock(false); - } - } elseif ($input_type->hasMixed() && $signature_param_type) { - $was_cloned = true; - $parent_nodes = $input_type->parent_nodes; - $by_ref = $input_type->by_ref; - $input_type = $signature_param_type->setProperties([ - 'ignore_nullable_issues' => $signature_param_type->isNullable(), - 'parent_nodes' => $parent_nodes, - 'by_ref' => $by_ref, - ]); - } + if ($input_type->isNullable() && !$param_type->isNullable()) { + $input_type = $input_type->getBuilder(); + $was_cloned = true; + $input_type->removeType('null'); + $input_type = $input_type->freeze(); + } - if ($context->inside_conditional && !isset($context->assigned_var_ids[$var_id])) { - $context->assigned_var_ids[$var_id] = 0; + if ($input_type->getId() === $param_type->getId()) { + if ($input_type->from_docblock) { + $input_type = $input_type->setFromDocblock(false); } + } elseif ($input_type->hasMixed() && $signature_param_type) { + $was_cloned = true; + $parent_nodes = $input_type->parent_nodes; + $by_ref = $input_type->by_ref; + $input_type = $signature_param_type->setProperties([ + 'ignore_nullable_issues' => $signature_param_type->isNullable(), + 'parent_nodes' => $parent_nodes, + 'by_ref' => $by_ref, + ]); + } - if ($was_cloned) { - $context->removeVarFromConflictingClauses($var_id, null, $statements_analyzer); - } + if ($context->inside_conditional && !isset($context->assigned_var_ids[$var_id])) { + $context->assigned_var_ids[$var_id] = 0; + } - if ($unpack) { - if ($unpacked_atomic_array instanceof TArray) { - $unpacked_atomic_array = $unpacked_atomic_array->setTypeParams([ - $unpacked_atomic_array->type_params[0], - $input_type, - ]); + if ($was_cloned) { + $context->removeVarFromConflictingClauses($var_id, null, $statements_analyzer); + } - $context->vars_in_scope[$var_id] = new Union([$unpacked_atomic_array]); - } elseif ($unpacked_atomic_array instanceof TKeyedArray - && $unpacked_atomic_array->is_list - ) { - if ($unpacked_atomic_array->isNonEmpty()) { - $unpacked_atomic_array = Type::getNonEmptyListAtomic($input_type); - } else { - $unpacked_atomic_array = Type::getListAtomic($input_type); - } + if ($unpack) { + if ($unpacked_atomic_array instanceof TArray) { + $unpacked_atomic_array = $unpacked_atomic_array->setTypeParams([ + $unpacked_atomic_array->type_params[0], + $input_type, + ]); - $context->vars_in_scope[$var_id] = new Union([$unpacked_atomic_array]); + $context->vars_in_scope[$var_id] = new Union([$unpacked_atomic_array]); + } elseif ($unpacked_atomic_array instanceof TKeyedArray + && $unpacked_atomic_array->is_list + ) { + if ($unpacked_atomic_array->isNonEmpty()) { + $unpacked_atomic_array = Type::getNonEmptyListAtomic($input_type); } else { - $context->vars_in_scope[$var_id] = new Union([ - new TArray([ - Type::getInt(), - $input_type, - ]), - ]); + $unpacked_atomic_array = Type::getListAtomic($input_type); } + + $context->vars_in_scope[$var_id] = new Union([$unpacked_atomic_array]); } else { - $context->vars_in_scope[$var_id] = $input_type; + $context->vars_in_scope[$var_id] = new Union([ + new TArray([ + Type::getInt(), + $input_type, + ]), + ]); } + } else { + $context->vars_in_scope[$var_id] = $input_type; } } @@ -1751,7 +1746,7 @@ private static function processTaintedness( Union $input_type, PhpParser\Node\Expr $expr, Context $context, - bool $specialize_taint + bool $specialize_taint, ): void { $codebase = $statements_analyzer->getCodebase(); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php index ac8016e2a20..a63d17f988e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentMapPopulator.php @@ -1,5 +1,7 @@ file_provider->getContents($statements_analyzer->getFilePath()); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php index 3d825668dc9..f5dd51a0166 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArgumentsAnalyzer.php @@ -1,5 +1,7 @@ inside_isset = $was_inside_isset; @@ -321,7 +321,7 @@ private static function handleArrayMapFilterArrayArg( int $argument_offset, PhpParser\Node\Arg $arg, Context $context, - ?TemplateResult &$template_result + ?TemplateResult &$template_result, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -376,7 +376,7 @@ private static function handleClosureArg( TemplateResult $template_result, int $argument_offset, PhpParser\Node\Arg $arg, - FunctionLikeParameter $param + FunctionLikeParameter $param, ): void { if (!$param->type) { return; @@ -456,7 +456,7 @@ private static function handleClosureArg( $statements_analyzer->getFilePath(), $closure_id, ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { return; } @@ -537,7 +537,6 @@ private static function handleClosureArg( /** * @param list $args - * @param string|MethodIdentifier|null $method_id * @param array $function_params * @return false|null * @psalm-suppress ComplexMethod there's just not much that can be done about this @@ -545,13 +544,13 @@ private static function handleClosureArg( public static function checkArgumentsMatch( StatementsAnalyzer $statements_analyzer, array $args, - $method_id, + string|MethodIdentifier|null $method_id, array $function_params, ?FunctionLikeStorage $function_storage, ?ClassLikeStorage $class_storage, TemplateResult $template_result, CodeLocation $code_location, - Context $context + Context $context, ): ?bool { $in_call_map = $method_id ? InternalCallMapHandler::inCallMap((string) $method_id) : false; @@ -994,7 +993,7 @@ private static function handlePossiblyMatchingByRefParam( int $argument_offset, PhpParser\Node\Arg $arg, Context $context, - ?TemplateResult $template_result + ?TemplateResult $template_result, ): ?bool { if ($arg->value instanceof PhpParser\Node\Scalar || $arg->value instanceof PhpParser\Node\Expr\Cast @@ -1045,9 +1044,9 @@ private static function handlePossiblyMatchingByRefParam( $function_params, static function ( ?FunctionLikeParameter $function_param, - FunctionLikeParameter $param + FunctionLikeParameter $param, ) use ( - $arg + $arg, ) { if ($param->name === $arg->name->name) { return $param; @@ -1144,7 +1143,7 @@ static function ( $by_ref_type, $by_ref_out_type ?: $by_ref_type, $context, - $method_id && (strpos($method_id, '::') !== false || !InternalCallMapHandler::inCallMap($method_id)), + $method_id && (str_contains($method_id, '::') || !InternalCallMapHandler::inCallMap($method_id)), $check_null_ref, ); } @@ -1158,7 +1157,7 @@ static function ( private static function evaluateArbitraryParam( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Arg $arg, - Context $context + Context $context, ): ?bool { // there are a bunch of things we want to evaluate even when we don't // know what function/method is being called @@ -1177,7 +1176,7 @@ private static function evaluateArbitraryParam( || $arg->value instanceof PhpParser\Node\Expr\Array_ || $arg->value instanceof PhpParser\Node\Expr\BinaryOp || $arg->value instanceof PhpParser\Node\Expr\Ternary - || $arg->value instanceof PhpParser\Node\Scalar\Encapsed + || $arg->value instanceof PhpParser\Node\Scalar\InterpolatedString || $arg->value instanceof PhpParser\Node\Expr\PostInc || $arg->value instanceof PhpParser\Node\Expr\PostDec || $arg->value instanceof PhpParser\Node\Expr\PreInc @@ -1266,7 +1265,7 @@ private static function handleByRefReadonlyArg( Context $context, PhpParser\Node\Expr\PropertyFetch $stmt, string $fq_class_name, - string $prop_name + string $prop_name, ): void { $property_id = $fq_class_name . '::$' . $prop_name; @@ -1279,7 +1278,7 @@ private static function handleByRefReadonlyArg( try { $declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_property_class); - } catch (InvalidArgumentException $_) { + } catch (InvalidArgumentException) { return; } @@ -1305,7 +1304,7 @@ private static function handleByRefFunctionArg( ?string $method_id, int $argument_offset, PhpParser\Node\Arg $arg, - Context $context + Context $context, ): ?bool { $var_id = ExpressionIdentifier::getVarId( $arg->value, @@ -1315,7 +1314,7 @@ private static function handleByRefFunctionArg( $builtin_array_functions = [ 'ksort', 'asort', 'krsort', 'arsort', 'natcasesort', 'natsort', - 'reset', 'end', 'next', 'prev', 'array_pop', 'array_shift', + 'reset', 'end', 'next', 'prev', 'array_pop', 'array_shift', 'extract', ]; if ($arg->value instanceof PhpParser\Node\Expr\PropertyFetch @@ -1482,7 +1481,7 @@ private static function getProvisionalTemplateResultForFunctionLike( ?TemplateResult $template_result, array $args, array $function_params, - ?FunctionLikeParameter $last_param + ?FunctionLikeParameter $last_param, ): ?TemplateResult { $template_types = CallAnalyzer::getTemplateTypesForCall( $codebase, @@ -1562,7 +1561,6 @@ private static function getProvisionalTemplateResultForFunctionLike( /** * @param array $args - * @param string|MethodIdentifier|null $method_id * @param array $function_params */ private static function checkArgCount( @@ -1575,9 +1573,9 @@ private static function checkArgCount( array $args, array $function_params, bool $in_call_map, - $method_id, + string|MethodIdentifier|null $method_id, ?string $cased_method_id, - CodeLocation $code_location + CodeLocation $code_location, ): void { if (!$is_variadic && count($args) > count($function_params) @@ -1632,14 +1630,8 @@ private static function checkArgCount( } foreach ($arg_value_type->getAtomicTypes() as $atomic_arg_type) { - if ($atomic_arg_type instanceof TList) { - $atomic_arg_type = $atomic_arg_type->getKeyedArray(); - } - $packed_var_definite_args_tmp = []; - if ($atomic_arg_type instanceof TCallableArray || - $atomic_arg_type instanceof TCallableKeyedArray - ) { + if ($atomic_arg_type instanceof TCallableKeyedArray) { $packed_var_definite_args_tmp[] = 2; } elseif ($atomic_arg_type instanceof TKeyedArray) { if ($atomic_arg_type->fallback_params !== null) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php index 55364514eb9..a8465bf5656 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ArrayFunctionArgumentsAnalyzer.php @@ -1,5 +1,7 @@ value; $nb_args = count($args); @@ -336,7 +336,7 @@ public static function handleAddition( public static function handleSplice( StatementsAnalyzer $statements_analyzer, array $args, - Context $context + Context $context, ): ?bool { $context->inside_call = true; $array_arg = $args[0]->value; @@ -459,12 +459,11 @@ public static function handleSplice( $length_min = (int) $length_literal->value; } } else { - $literals = array_merge( - $length_arg_type->getLiteralStrings(), - $length_arg_type->getLiteralInts(), - $length_arg_type->getLiteralFloats(), - ); - foreach ($literals as $literal) { + foreach ([ + ...$length_arg_type->getLiteralStrings(), + ...$length_arg_type->getLiteralInts(), + ...$length_arg_type->getLiteralFloats(), + ] as $literal) { if ($literal->isNumericType() && ($literal_val = (int) $literal->value) && ((isset($length_min) && $length_min> $literal_val) || !isset($length_min))) { @@ -620,7 +619,7 @@ public static function handleByRefArrayAdjustment( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Arg $arg, Context $context, - bool $is_array_shift + bool $is_array_shift, ): void { $var_id = ExpressionIdentifier::getVarId( $arg->value, @@ -635,10 +634,6 @@ public static function handleByRefArrayAdjustment( $array_atomic_types = []; foreach ($context->vars_in_scope[$var_id]->getAtomicTypes() as $array_atomic_type) { - if ($array_atomic_type instanceof TList) { - $array_atomic_type = $array_atomic_type->getKeyedArray(); - } - if ($array_atomic_type instanceof TKeyedArray) { if ($is_array_shift && $array_atomic_type->is_list && !$context->inside_loop @@ -743,7 +738,7 @@ private static function checkClosureType( int $min_closure_param_count, int $max_closure_param_count, array $array_arg_types, - bool $check_functions + bool $check_functions, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -769,7 +764,7 @@ private static function checkClosureType( foreach ($function_ids as $function_id) { $function_id = strtolower($function_id); - if (strpos($function_id, '::') !== false) { + if (str_contains($function_id, '::')) { if ($function_id[0] === '$') { $function_id = substr($function_id, 1); } @@ -807,7 +802,7 @@ private static function checkClosureType( try { $method_storage = $codebase->methods->getStorage($function_id_part); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { // the method may not exist, but we're suppressing that issue continue; } @@ -908,7 +903,7 @@ private static function checkClosureTypeArgs( PhpParser\Node\Arg $closure_arg, int $min_closure_param_count, int $max_closure_param_count, - array $array_arg_types + array $array_arg_types, ): void { $codebase = $statements_analyzer->getCodebase(); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php index 367f2d26d27..7a9f34f9fdf 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/ClassTemplateParamCollector.php @@ -1,5 +1,7 @@ is_trait ? $static_class_storage @@ -185,7 +187,7 @@ private static function resolveTemplateParam( Union $input_type_extends, ClassLikeStorage $static_class_storage, TGenericObject $lhs_type_part, - ?TemplateResult $template_result = null + ?TemplateResult $template_result = null, ): ?Union { $output_type_extends = null; foreach ($input_type_extends->getAtomicTypes() as $type_extends_atomic) { @@ -259,7 +261,7 @@ private static function expandType( Union $input_type_extends, array $e, string $static_fq_class_name, - ?array $static_template_types + ?array $static_template_types, ): array { $output_type_extends = []; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php index 98e192c72f7..76501316200 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallAnalyzer.php @@ -1,5 +1,7 @@ name; @@ -237,10 +238,7 @@ public static function analyze( $function_call_info->function_id, ); - $template_result->lower_bounds = array_merge( - $template_result->lower_bounds, - $already_inferred_lower_bounds, - ); + $template_result->lower_bounds = [...$template_result->lower_bounds, ...$already_inferred_lower_bounds]; if ($function_name instanceof PhpParser\Node\Name && $function_call_info->function_id) { $stmt_type = FunctionCallReturnTypeFetcher::fetch( @@ -428,7 +426,7 @@ private static function handleNamedFunction( PhpParser\Node\Expr\FuncCall $stmt, PhpParser\Node\Name $function_name, Context $context, - CodeLocation $code_location + CodeLocation $code_location, ): FunctionCallInfo { $function_call_info = new FunctionCallInfo(); @@ -561,7 +559,7 @@ private static function handleNamedFunction( $function_call_info->defined_constants = $function_storage->defined_constants; $function_call_info->global_variables = $function_storage->global_variables; } - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { $function_call_info->function_params = [ new FunctionLikeParameter('args', false, null, null, null, null, false, false, true), ]; @@ -607,7 +605,7 @@ private static function getAnalyzeNamedExpression( PhpParser\Node\Expr\FuncCall $stmt, PhpParser\Node\Expr\FuncCall $real_stmt, PhpParser\Node\Expr $function_name, - Context $context + Context $context, ): FunctionCallInfo { $function_call_info = new FunctionCallInfo(); @@ -662,9 +660,7 @@ private static function getAnalyzeNamedExpression( continue; } - if ($var_type_part instanceof TList) { - $var_type_part = $var_type_part->getKeyedArray(); - } + if ($var_type_part instanceof TClosure || $var_type_part instanceof TCallable) { if (!$var_type_part->is_pure) { @@ -764,7 +760,7 @@ private static function getAnalyzeNamedExpression( if (strpos($var_type_part->value, '::')) { $parts = explode('::', strtolower($var_type_part->value)); $fq_class_name = $parts[0]; - $fq_class_name = preg_replace('/^\\\/', '', $fq_class_name, 1); + $fq_class_name = (string) preg_replace('/^\\\/', '', $fq_class_name, 1); $potential_method_id = new MethodIdentifier($fq_class_name, $parts[1]); } else { $function_call_info->new_function_name = new VirtualFullyQualified( @@ -882,7 +878,7 @@ private static function analyzeInvokeCall( PhpParser\Node\Expr\FuncCall $real_stmt, PhpParser\Node\Expr $function_name, Context $context, - Atomic $atomic_type + Atomic $atomic_type, ): void { $old_data_provider = $statements_analyzer->node_data; @@ -938,7 +934,7 @@ private static function processAssertFunctionEffects( Codebase $codebase, PhpParser\Node\Expr\FuncCall $stmt, PhpParser\Node\Arg $first_arg, - Context $context + Context $context, ): void { $first_arg_value_id = spl_object_id($first_arg->value); @@ -1030,7 +1026,7 @@ private static function checkFunctionCallPurity( PhpParser\Node\Expr\FuncCall $stmt, PhpParser\Node $function_name, FunctionCallInfo $function_call_info, - Context $context + Context $context, ): void { $config = $codebase->config; @@ -1118,7 +1114,7 @@ private static function checkFunctionCallPurity( private static function callUsesByReferenceArguments( FunctionCallInfo $function_call_info, - PhpParser\Node\Expr\FuncCall $stmt + PhpParser\Node\Expr\FuncCall $stmt, ): bool { // If the function doesn't have any by-reference parameters // we shouldn't look any further. diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallInfo.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallInfo.php index 9bcec9ed740..0af657998bf 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallInfo.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/FunctionCallInfo.php @@ -1,5 +1,7 @@ config; @@ -234,7 +235,7 @@ public static function fetch( ); } } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // this can happen when the function was defined in the Config startup script $stmt_type = Type::getMixed(); } @@ -280,7 +281,7 @@ public static function fetch( $fake_call_factory = new BuilderFactory(); - if (strpos($proxy_call['fqn'], '::') !== false) { + if (str_contains($proxy_call['fqn'], '::')) { [$fqcn, $method] = explode('::', $proxy_call['fqn']); $fake_call = $fake_call_factory->staticCall($fqcn, $method, $fake_call_arguments); } else { @@ -316,7 +317,7 @@ private static function getReturnTypeFromCallMapWithArgs( string $function_id, array $call_args, TCallable $callmap_callable, - Context $context + Context $context, ): Union { $call_map_key = strtolower($function_id); @@ -361,12 +362,7 @@ private static function getReturnTypeFromCallMapWithArgs( if (count($atomic_types) === 1) { if (isset($atomic_types['array'])) { - if ($atomic_types['array'] instanceof TList) { - $atomic_types['array'] = $atomic_types['array']->getKeyedArray(); - } - if ($atomic_types['array'] instanceof TCallableArray - || $atomic_types['array'] instanceof TCallableKeyedArray - ) { + if ($atomic_types['array'] instanceof TCallableKeyedArray) { return Type::getInt(false, 2); } @@ -542,7 +538,7 @@ private static function taintReturnType( FunctionLikeStorage $function_storage, Union &$stmt_type, TemplateResult $template_result, - Context $context + Context $context, ): ?DataFlowNode { if (!$statements_analyzer->data_flow_graph) { return null; @@ -642,7 +638,7 @@ private static function taintReturnType( $pattern = trim($pattern); if ($pattern[0] === '[' && $pattern[1] === '^' - && substr($pattern, -1) === ']' + && str_ends_with($pattern, ']') ) { $pattern = substr($pattern, 2, -1); @@ -706,7 +702,7 @@ public static function taintUsingFlows( CodeLocation $node_location, DataFlowNode $function_call_node, array $removed_taints, - array $added_taints = [] + array $added_taints = [], ): void { foreach ($function_storage->return_source_params as $i => $path_type) { if (!isset($args[$i])) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php index 14a947f517a..4822d894c99 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgHandler.php @@ -21,7 +21,7 @@ use UnexpectedValueException; use function is_string; -use function strpos; +use function str_contains; use function strtolower; /** @@ -59,7 +59,7 @@ public static function remapLowerBounds( StatementsAnalyzer $statements_analyzer, TemplateResult $inferred_template_result, HighOrderFunctionArgInfo $input_function, - Union $container_function_type + Union $container_function_type, ): TemplateResult { // Try to infer container callable by $inferred_template_result $container_type = TemplateInferredTypeReplacer::replace( @@ -116,7 +116,7 @@ public static function enhanceCallableArgType( PhpParser\Node\Expr $arg_expr, StatementsAnalyzer $statements_analyzer, HighOrderFunctionArgInfo $high_order_callable_info, - TemplateResult $high_order_template_result + TemplateResult $high_order_template_result, ): void { // Psalm can infer simple callable/closure. // But can't infer first-class-callable or high-order function. @@ -152,7 +152,7 @@ public static function getCallableArgInfo( Context $context, PhpParser\Node\Expr $input_arg_expr, StatementsAnalyzer $statements_analyzer, - FunctionLikeParameter $container_param + FunctionLikeParameter $container_param, ): ?HighOrderFunctionArgInfo { if (!self::isSupported($container_param)) { return null; @@ -274,7 +274,7 @@ public static function getCallableArgInfo( $class_storage, ); } - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { return null; } @@ -293,8 +293,7 @@ private static function isSupported(FunctionLikeParameter $container_param): boo return false; } - if ($a instanceof Type\Atomic\TCallableArray || - $a instanceof Type\Atomic\TCallableString || + if ($a instanceof Type\Atomic\TCallableString || $a instanceof Type\Atomic\TCallableKeyedArray ) { return false; @@ -306,7 +305,7 @@ private static function isSupported(FunctionLikeParameter $container_param): boo private static function fromLiteralString( Union $constant, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): ?HighOrderFunctionArgInfo { $literal = $constant->isSingle() ? $constant->getSingleAtomic() : null; @@ -318,7 +317,7 @@ private static function fromLiteralString( return new HighOrderFunctionArgInfo( HighOrderFunctionArgInfo::TYPE_STRING_CALLABLE, - strpos($literal->value, '::') !== false + str_contains($literal->value, '::') ? $codebase->methods->getStorage(MethodIdentifier::wrap($literal->value)) : $codebase->functions->getStorage($statements_analyzer, strtolower($literal->value)), ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgInfo.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgInfo.php index 87019029000..6b3203ab3ba 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgInfo.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/HighOrderFunctionArgInfo.php @@ -12,8 +12,6 @@ use Psalm\Type\Atomic\TClosure; use Psalm\Type\Union; -use function array_merge; - /** * @internal */ @@ -24,31 +22,20 @@ final class HighOrderFunctionArgInfo public const TYPE_STRING_CALLABLE = 'string-callable'; public const TYPE_CALLABLE = 'callable'; - /** @psalm-var HighOrderFunctionArgInfo::TYPE_* */ - private string $type; - private FunctionLikeStorage $function_storage; - private ?ClassLikeStorage $class_storage; - /** * @psalm-param HighOrderFunctionArgInfo::TYPE_* $type */ public function __construct( - string $type, - FunctionLikeStorage $function_storage, - ?ClassLikeStorage $class_storage = null + private readonly string $type, + private readonly FunctionLikeStorage $function_storage, + private readonly ?ClassLikeStorage $class_storage = null, ) { - $this->type = $type; - $this->function_storage = $function_storage; - $this->class_storage = $class_storage; } public function getTemplates(): TemplateResult { $templates = $this->class_storage - ? array_merge( - $this->function_storage->template_types ?? [], - $this->class_storage->template_types ?? [], - ) + ? [...$this->function_storage->template_types ?? [], ...$this->class_storage->template_types ?? []] : $this->function_storage->template_types ?? []; return new TemplateResult($templates, []); @@ -61,30 +48,24 @@ public function getType(): string public function getFunctionType(): Union { - switch ($this->type) { - case self::TYPE_FIRST_CLASS_CALLABLE: - return new Union([ - new TClosure( - 'Closure', - $this->function_storage->params, - $this->function_storage->return_type, - $this->function_storage->pure, - ), - ]); - - case self::TYPE_STRING_CALLABLE: - case self::TYPE_CLASS_CALLABLE: - return new Union([ - new TCallable( - 'callable', - $this->function_storage->params, - $this->function_storage->return_type, - $this->function_storage->pure, - ), - ]); - - default: - return $this->function_storage->return_type ?? Type::getMixed(); - } + return match ($this->type) { + self::TYPE_FIRST_CLASS_CALLABLE => new Union([ + new TClosure( + 'Closure', + $this->function_storage->params, + $this->function_storage->return_type, + $this->function_storage->pure, + ), + ]), + self::TYPE_STRING_CALLABLE, self::TYPE_CLASS_CALLABLE => new Union([ + new TCallable( + 'callable', + $this->function_storage->params, + $this->function_storage->return_type, + $this->function_storage->pure, + ), + ]), + default => $this->function_storage->return_type ?? Type::getMixed(), + }; } } 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 04da740e01c..419ab0ecbf6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicCallContext.php @@ -1,5 +1,7 @@ */ - public array $args; - /** @param list $args */ - public function __construct(MethodIdentifier $method_id, array $args) + public function __construct(public MethodIdentifier $method_id, public array $args) { - $this->method_id = $method_id; - $this->args = $args; } } 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 a4e6d2153fb..46927735f74 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php @@ -1,5 +1,7 @@ as->isMixed() @@ -537,7 +538,7 @@ private static function getIntersectionReturnType( Atomic $lhs_type_part, ?string $lhs_var_id, AtomicMethodCallAnalysisResult $result, - array $intersection_types + array $intersection_types, ): array { $all_intersection_return_type = null; $all_intersection_existent_method_ids = []; @@ -594,7 +595,7 @@ private static function updateResultReturnType( AtomicMethodCallAnalysisResult $result, Union $return_type_candidate, ?Union $all_intersection_return_type, - Codebase $codebase + Codebase $codebase, ): void { if ($all_intersection_return_type) { $return_type_candidate = Type::intersectUnionTypes( @@ -616,9 +617,9 @@ private static function handleInvalidClass( ?string $lhs_var_id, Context $context, bool $is_intersection, - AtomicMethodCallAnalysisResult $result + AtomicMethodCallAnalysisResult $result, ): void { - switch (get_class($lhs_type_part)) { + switch ($lhs_type_part::class) { case TNull::class: case TFalse::class: // handled above @@ -737,7 +738,7 @@ private static function handleTemplatedMixins( StatementsSource $source, PhpParser\Node\Expr\MethodCall $stmt, StatementsAnalyzer $statements_analyzer, - string $fq_class_name + string $fq_class_name, ): array { $naive_method_exists = false; @@ -827,7 +828,7 @@ private static function handleRegularMixins( PhpParser\Node\Expr\MethodCall $stmt, StatementsAnalyzer $statements_analyzer, string $fq_class_name, - ?string $lhs_var_id + ?string $lhs_var_id, ): array { $naive_method_exists = false; @@ -915,7 +916,7 @@ private static function handleCallableObject( Context $context, ?TCallable $lhs_type_part_callable, AtomicMethodCallAnalysisResult $result, - ?TemplateResult $inferred_template_result = null + ?TemplateResult $inferred_template_result = null, ): void { $method_id = 'object::__invoke'; $result->existent_method_ids[$method_id] = true; 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 6fe0bd3cf80..9cfab831cbc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php @@ -1,5 +1,7 @@ config; @@ -204,7 +206,7 @@ public static function analyze( try { $method_storage = $codebase->methods->getStorage($declaring_method_id ?? $method_id); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { $method_storage = null; } @@ -445,7 +447,7 @@ public static function analyze( $possibilities = array_filter( $possibilities, static fn(Possibilities $assertion): bool => !(is_string($assertion->var_id) - && strpos($assertion->var_id, '$this->') === 0 + && str_starts_with($assertion->var_id, '$this->') ), ); } @@ -468,7 +470,7 @@ public static function analyze( $possibilities = array_filter( $possibilities, static fn(Possibilities $assertion): bool => !(is_string($assertion->var_id) - && strpos($assertion->var_id, '$this->') === 0 + && str_starts_with($assertion->var_id, '$this->') ), ); } @@ -543,7 +545,7 @@ private static function getMagicGetterOrSetterProperty( PhpParser\Node\Expr\MethodCall $stmt, PhpParser\Node\Identifier $stmt_name, Context $context, - string $fq_class_name + string $fq_class_name, ): ?Union { $method_name = strtolower($stmt_name->name); if (!in_array($method_name, ['__get', '__set'], true)) { 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 782062ea292..3953e4908ae 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallProhibitionAnalyzer.php @@ -1,5 +1,7 @@ methods; 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 a23332ee3ca..9da3c08543c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallPurityAnalyzer.php @@ -1,5 +1,7 @@ external_mutation_free && $statements_analyzer->node_data->isPureCompatible($stmt->var); 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 84021ca3651..3d3b39e5b37 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodCallReturnTypeFetcher.php @@ -1,5 +1,7 @@ data_flow_graph || !$declaring_method_id @@ -560,7 +562,7 @@ public static function replaceTemplateTypes( TemplateResult $template_result, MethodIdentifier $method_id, int $arg_count, - Codebase $codebase + Codebase $codebase, ): Union { if ($template_result->template_types) { $bindable_template_types = $return_type_candidate->getTemplateTypes(); @@ -575,7 +577,7 @@ public static function replaceTemplateTypes( ) { if ($template_type->param_name === 'TFunctionArgCount') { $template_result->lower_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, $arg_count), ), @@ -583,7 +585,7 @@ public static function replaceTemplateTypes( ]; } elseif ($template_type->param_name === 'TPhpMajorVersion') { $template_result->lower_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, $codebase->getMajorAnalysisPhpVersion()), ), @@ -591,7 +593,7 @@ public static function replaceTemplateTypes( ]; } elseif ($template_type->param_name === 'TPhpVersionId') { $template_result->lower_bounds[$template_type->param_name] = [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt( false, 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 e96edfd58cf..8e047dedebd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MethodVisibilityAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); $codebase_methods = $codebase->methods; 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 22f483c1bcd..a5048bd2f3b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -1,5 +1,7 @@ fq_class_name; $method_name_lc = $method_id->method_name; @@ -201,7 +203,7 @@ public static function handleMagicMethod( $result->existent_method_ids[$method_id->__toString()] = true; $array_values = array_map( - static fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( + static fn(PhpParser\Node\Arg $arg): PhpParser\Node\ArrayItem => new VirtualArrayItem( $arg->value, null, false, @@ -250,7 +252,7 @@ public static function handleMissingOrMagicMethod( ?string $intersection_method_id, string $cased_method_id, AtomicMethodCallAnalysisResult $result, - ?Atomic $lhs_type_part + ?Atomic $lhs_type_part, ): void { $fq_class_name = $method_id->fq_class_name; $method_name_lc = $method_id->method_name; @@ -417,7 +419,7 @@ private static function createFirstClassCallableReturnType(?MethodStorage $metho private static function findPseudoMethodAndClassStorages( Codebase $codebase, ClassLikeStorage $static_class_storage, - string $method_name_lc + string $method_name_lc, ): ?array { if (isset($static_class_storage->declaring_pseudo_method_ids[$method_name_lc])) { $method_id = $static_class_storage->declaring_pseudo_method_ids[$method_name_lc]; @@ -433,6 +435,12 @@ private static function findPseudoMethodAndClassStorages( } $ancestors = $static_class_storage->class_implements; + foreach ($static_class_storage->namedMixins as $namedObject) { + $type = $namedObject->value; + if ($type) { + $ancestors[$type] = true; + } + } foreach ($ancestors as $fq_class_name => $_) { $class_storage = $codebase->classlikes->getStorageFor($fq_class_name); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php index 53bbb8097bf..4b386a0ee66 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/MethodCallAnalyzer.php @@ -1,5 +1,7 @@ inside_call; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php index 5f1900c2b78..f29b75fbb5a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NamedFunctionCallHandler.php @@ -1,5 +1,7 @@ isSingle() ) { foreach ($array_type_union->getAtomicTypes() as $array_type) { - if ($array_type instanceof TList) { - $array_type = $array_type->getKeyedArray(); - } - if ($array_type instanceof TKeyedArray) { foreach ($array_type->properties as $key => $type) { // variables must start with letters or underscore @@ -514,7 +512,7 @@ public static function handle( if ($first_arg && $function_id - && strpos($function_id, 'is_') === 0 + && str_starts_with($function_id, 'is_') && $function_id !== 'is_a' && !$context->inside_negation ) { @@ -680,7 +678,7 @@ private static function handleDependentTypeFunction( PhpParser\Node\Expr\FuncCall $stmt, PhpParser\Node\Expr\FuncCall $real_stmt, string $function_id, - Context $context + Context $context, ): void { $first_arg = $stmt->getArgs()[0] ?? null; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php index 8f35fee56b3..f4b7e5c09f7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/NewAnalyzer.php @@ -1,5 +1,7 @@ classlike_storage_provider->get($fq_class_name); @@ -353,7 +355,7 @@ private static function analyzeNamedConstructor( if ($storage->abstract && !$can_extend) { if (IssueBuffer::accepts( new AbstractInstantiation( - 'Unable to instantiate a abstract class ' . $fq_class_name, + 'Unable to instantiate an abstract class ' . $fq_class_name, new CodeLocation($statements_analyzer->getSource(), $stmt), ), $statements_analyzer->getSuppressedIssues(), @@ -688,7 +690,7 @@ private static function analyzeConstructorExpression( PhpParser\Node\Expr $stmt_class, Config $config, ?string &$fq_class_name, - bool &$can_extend + bool &$can_extend, ): void { $was_inside_general_use = $context->inside_general_use; $context->inside_general_use = true; @@ -795,7 +797,7 @@ private static function getNewType( PhpParser\Node\Expr\New_ $stmt, Union $stmt_class_type, Config $config, - bool &$can_extend + bool &$can_extend, ): ?Union { $new_types = []; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php index e78cfd8d230..007886756e3 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticCallAnalyzer.php @@ -1,5 +1,7 @@ data_flow_graph) { return; 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 c9dce4d1460..156424de827 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/AtomicStaticCallAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -893,7 +895,7 @@ private static function checkPseudoMethod( array $args, ClassLikeStorage $class_storage, MethodStorage $pseudo_method_storage, - Context $context + Context $context, ): ?bool { if (ArgumentsAnalyzer::analyze( $statements_analyzer, @@ -948,7 +950,7 @@ private static function checkPseudoMethod( new CodeLocation($statements_analyzer, $stmt), $context, ); - } catch (Exception $e) { + } catch (Exception) { // do nothing } } @@ -996,7 +998,7 @@ public static function handleNonObjectCall( PhpParser\Node\Expr\StaticCall $stmt, Context $context, Atomic $lhs_type_part, - bool $ignore_nullable_issues + bool $ignore_nullable_issues, ): void { $codebase = $statements_analyzer->getCodebase(); $config = $codebase->config; @@ -1071,7 +1073,7 @@ public static function handleNonObjectCall( private static function findPseudoMethodAndClassStorages( Codebase $codebase, ClassLikeStorage $static_class_storage, - string $method_name_lc + string $method_name_lc, ): ?array { if ($pseudo_method_storage = $static_class_storage->pseudo_static_methods[$method_name_lc] ?? null) { return [$pseudo_method_storage, $static_class_storage]; @@ -1108,7 +1110,7 @@ private static function forwardCallToInstanceMethod( PhpParser\Node\Identifier $stmt_name, Context $context, string $virtual_var_name = 'this', - bool $always_set_node_type = false + bool $always_set_node_type = false, ): bool { $old_data_provider = $statements_analyzer->node_data; 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 23c0078facd..c7dc29376ad 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/StaticMethod/ExistingAtomicStaticCallAnalyzer.php @@ -1,5 +1,7 @@ fq_class_name; $method_name_lc = $method_id->method_name; @@ -111,22 +114,18 @@ public static function analyze( $local_vars_possibly_in_scope = []; foreach ($context->vars_in_scope as $var => $_) { - if (strpos($var, '$this->') !== 0 && $var !== '$this') { + if (!str_starts_with($var, '$this->') && $var !== '$this') { $local_vars_in_scope[$var] = $context->vars_in_scope[$var]; } } foreach ($context->vars_possibly_in_scope as $var => $_) { - if (strpos($var, '$this->') !== 0 && $var !== '$this') { + if (!str_starts_with($var, '$this->') && $var !== '$this') { $local_vars_possibly_in_scope[$var] = $context->vars_possibly_in_scope[$var]; } } if (!isset($context->initialized_methods[(string) $appearing_method_id])) { - if ($context->initialized_methods === null) { - $context->initialized_methods = []; - } - $context->initialized_methods[(string) $appearing_method_id] = true; $file_analyzer->getMethodMutations($appearing_method_id, $context); @@ -482,7 +481,7 @@ private static function getMethodReturnType( Context $context, string $fq_class_name, ClassLikeStorage $class_storage, - Config $config + Config $config, ): ?Union { $return_type_candidate = $codebase->methods->getMethodReturnType( $method_id, @@ -623,11 +622,11 @@ private static function resolveTemplateResultLowerBound( StaticCall $stmt, ClassLikeStorage $class_storage, MethodIdentifier $method_id, - TTemplateParam $template_type + TTemplateParam $template_type, ): array { if ($template_type->param_name === 'TFunctionArgCount') { return [ - 'fn-' . strtolower((string)$method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, count($stmt->getArgs())), ), @@ -637,7 +636,7 @@ private static function resolveTemplateResultLowerBound( if ($template_type->param_name === 'TPhpMajorVersion') { return [ - 'fn-' . strtolower((string)$method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt(false, $codebase->getMajorAnalysisPhpVersion()), ), @@ -647,7 +646,7 @@ private static function resolveTemplateResultLowerBound( if ($template_type->param_name === 'TPhpVersionId') { return [ - 'fn-' . strtolower((string) $method_id) => [ + 'fn-' . $method_id->method_name => [ new TemplateBound( Type::getInt( false, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php index a547b291cb5..6746e21027d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CallAnalyzer.php @@ -1,5 +1,7 @@ getFQCLN(); @@ -107,10 +110,6 @@ public static function collectSpecialInformation( return; } - if ($context->initialized_methods === null) { - $context->initialized_methods = []; - } - $context->initialized_methods[(string) $method_id] = true; } @@ -190,10 +189,6 @@ public static function collectSpecialInformation( return; } - if ($context->initialized_methods === null) { - $context->initialized_methods = []; - } - $context->initialized_methods[(string) $declaring_method_id] = true; $method_storage = $codebase->methods->getStorage($declaring_method_id); @@ -225,7 +220,7 @@ public static function collectSpecialInformation( $local_vars_in_scope = []; foreach ($context->vars_in_scope as $var_id => $type) { - if (strpos($var_id, '$this->') === 0) { + if (str_starts_with($var_id, '$this->')) { if ($type->initialized) { $local_vars_in_scope[$var_id] = $context->vars_in_scope[$var_id]; @@ -278,7 +273,7 @@ public static function checkMethodArgs( TemplateResult $template_result, Context $context, CodeLocation $code_location, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): bool { $codebase = $statements_analyzer->getCodebase(); @@ -309,7 +304,6 @@ public static function checkMethodArgs( $declaring_method_id = $class_storage->declaring_method_ids[$method_name]; $declaring_fq_class_name = $declaring_method_id->fq_class_name; - $declaring_method_name = $declaring_method_id->method_name; if ($declaring_fq_class_name !== $fq_class_name) { $declaring_class_storage = $codebase->classlike_storage_provider->get($declaring_fq_class_name); @@ -317,11 +311,7 @@ public static function checkMethodArgs( $declaring_class_storage = $class_storage; } - if (!isset($declaring_class_storage->methods[$declaring_method_name])) { - throw new UnexpectedValueException('Storage should not be empty here'); - } - - $method_storage = $declaring_class_storage->methods[$declaring_method_name]; + $method_storage = $codebase->methods->getStorage($declaring_method_id); if ($declaring_class_storage->user_defined && !$method_storage->has_docblock_param_types @@ -393,7 +383,7 @@ public static function getTemplateTypesForCall( ?string $appearing_class_name, ?ClassLikeStorage $calling_class_storage, array $existing_template_types = [], - array $class_template_params = [] + array $class_template_params = [], ): array { $template_types = $existing_template_types; @@ -465,7 +455,7 @@ public static function getGenericParamForOffset( string $fq_class_name, string $template_name, array $template_extended_params, - array $found_generic_params + array $found_generic_params, ): Union { if (isset($found_generic_params[$template_name][$fq_class_name])) { return $found_generic_params[$template_name][$fq_class_name]; @@ -498,7 +488,7 @@ public static function getGenericParamForOffset( */ public static function getFunctionIdsFromCallableArg( FileSource $file_source, - PhpParser\Node\Expr $callable_arg + PhpParser\Node\Expr $callable_arg, ): array { if ($callable_arg instanceof PhpParser\Node\Expr\BinaryOp\Concat) { if ($callable_arg->left instanceof PhpParser\Node\Expr\ClassConstFetch @@ -518,7 +508,7 @@ public static function getFunctionIdsFromCallableArg( } if ($callable_arg instanceof PhpParser\Node\Scalar\String_) { - $potential_id = preg_replace('/^\\\/', '', $callable_arg->value, 1); + $potential_id = (string) preg_replace('/^\\\/', '', $callable_arg->value, 1); if (preg_match('/^[A-Za-z0-9_]+(\\\[A-Za-z0-9_]+)*(::[A-Za-z0-9_]+)?$/', $potential_id)) { assert($potential_id !== ''); @@ -603,7 +593,7 @@ public static function checkFunctionExists( StatementsAnalyzer $statements_analyzer, string &$function_id, CodeLocation $code_location, - bool $can_be_in_root_scope + bool $can_be_in_root_scope, ): bool { $cased_function_id = $function_id; $function_id = strtolower($function_id); @@ -612,7 +602,7 @@ public static function checkFunctionExists( if (!$codebase->functions->functionExists($statements_analyzer, $function_id)) { /** @var non-empty-lowercase-string */ - $root_function_id = preg_replace('/.*\\\/', '', $function_id); + $root_function_id = (string) preg_replace('/.*\\\/', '', $function_id); if ($can_be_in_root_scope && $function_id !== $root_function_id @@ -648,7 +638,7 @@ public static function applyAssertionsToContext( array $args, TemplateResult $template_result, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): void { $type_assertions = []; @@ -673,16 +663,16 @@ public static function applyAssertionsToContext( } } elseif ($var_possibilities->var_id === '$this' && $thisName !== null) { $assertion_var_id = $thisName; - } elseif (strpos($var_possibilities->var_id, '$this->') === 0 && $thisName !== null) { + } elseif (str_starts_with($var_possibilities->var_id, '$this->') && $thisName !== null) { $assertion_var_id = $thisName . str_replace('$this->', '->', $var_possibilities->var_id); - } elseif (strpos($var_possibilities->var_id, 'self::') === 0 && $context->self) { + } elseif (str_starts_with($var_possibilities->var_id, 'self::') && $context->self) { $assertion_var_id = $context->self . str_replace('self::', '::', $var_possibilities->var_id); - } elseif (strpos($var_possibilities->var_id, '::$') !== false) { + } elseif (str_contains($var_possibilities->var_id, '::$')) { // allow assertions to bring external static props into scope $assertion_var_id = $var_possibilities->var_id; } elseif (isset($context->vars_in_scope[$var_possibilities->var_id])) { $assertion_var_id = $var_possibilities->var_id; - } elseif (strpos($var_possibilities->var_id, '->') !== false) { + } elseif (str_contains($var_possibilities->var_id, '->')) { $exploded = explode('->', $var_possibilities->var_id); if (count($exploded) < 2) { @@ -879,7 +869,7 @@ public static function applyAssertionsToContext( $simplified_clauses, ); - $type_assertions = array_merge($type_assertions, $assert_type_assertions); + $type_assertions = [...$type_assertions, ...$assert_type_assertions]; } } @@ -973,7 +963,7 @@ public static function checkTemplateResult( StatementsAnalyzer $statements_analyzer, TemplateResult $template_result, CodeLocation $code_location, - ?string $function_id + ?string $function_id, ): void { if ($template_result->lower_bounds && $template_result->upper_bounds) { foreach ($template_result->upper_bounds as $template_name => $defining_map) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php index dbb659f9ff6..8cb088d450b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CastAnalyzer.php @@ -1,5 +1,7 @@ expr, $context) === false) { @@ -202,9 +202,6 @@ public static function analyze( $all_permissible = true; foreach ($stmt_expr_type->getAtomicTypes() as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type instanceof Scalar) { $objWithProps = new TObjectWithProperties(['scalar' => new Union([$type])]); $permissible_atomic_types[] = $objWithProps; @@ -249,9 +246,6 @@ public static function analyze( $all_permissible = true; foreach ($stmt_expr_type->getAtomicTypes() as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type instanceof Scalar) { $keyed_array = new TKeyedArray([new Union([$type])], null, null, true); $permissible_atomic_types[] = $keyed_array; @@ -306,7 +300,7 @@ public static function analyze( IssueBuffer::maybeAdd( new UnrecognizedExpression( - 'Psalm does not understand the cast ' . get_class($stmt), + 'Psalm does not understand the cast ' . $stmt::class, new CodeLocation($statements_analyzer->getSource(), $stmt), ), $statements_analyzer->getSuppressedIssues(), @@ -319,7 +313,7 @@ public static function castIntAttempt( StatementsAnalyzer $statements_analyzer, Union $stmt_type, PhpParser\Node\Expr $stmt, - bool $explicit_cast = false + bool $explicit_cast = false, ): Union { $codebase = $statements_analyzer->getCodebase(); @@ -339,10 +333,6 @@ public static function castIntAttempt( while ($atomic_types) { $atomic_type = array_pop($atomic_types); - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } - if ($atomic_type instanceof TInt) { $valid_ints[] = $atomic_type; @@ -405,7 +395,7 @@ public static function castIntAttempt( $intersection_types = [$atomic_type]; if ($atomic_type->extra_types) { - $intersection_types = array_merge($intersection_types, $atomic_type->extra_types); + $intersection_types = [...$intersection_types, ...$atomic_type->extra_types]; } foreach ($intersection_types as $intersection_type) { @@ -509,7 +499,7 @@ public static function castFloatAttempt( StatementsAnalyzer $statements_analyzer, Union $stmt_type, PhpParser\Node\Expr $stmt, - bool $explicit_cast = false + bool $explicit_cast = false, ): Union { $codebase = $statements_analyzer->getCodebase(); @@ -529,10 +519,6 @@ public static function castFloatAttempt( while ($atomic_types) { $atomic_type = array_pop($atomic_types); - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } - if ($atomic_type instanceof TFloat) { $valid_floats[] = $atomic_type; @@ -606,7 +592,7 @@ public static function castFloatAttempt( $intersection_types = [$atomic_type]; if ($atomic_type->extra_types) { - $intersection_types = array_merge($intersection_types, $atomic_type->extra_types); + $intersection_types = [...$intersection_types, ...$atomic_type->extra_types]; } foreach ($intersection_types as $intersection_type) { @@ -711,7 +697,7 @@ public static function castStringAttempt( Context $context, Union $stmt_type, PhpParser\Node\Expr $stmt, - bool $explicit_cast = false + bool $explicit_cast = false, ): Union { $codebase = $statements_analyzer->getCodebase(); @@ -907,7 +893,7 @@ public static function castStringAttempt( private static function checkExprGeneralUse( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr\Cast $stmt, - Context $context + Context $context, ): bool { $was_inside_general_use = $context->inside_general_use; $context->inside_general_use = true; @@ -919,7 +905,7 @@ private static function checkExprGeneralUse( private static function handleRedundantCast( Union $maybe_type, StatementsAnalyzer $statements_analyzer, - PhpParser\Node\Expr\Cast $stmt + PhpParser\Node\Expr\Cast $stmt, ): void { $codebase = $statements_analyzer->getCodebase(); $project_analyzer = $statements_analyzer->getProjectAnalyzer(); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php index 41c04ed79c0..6ef381a382c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ClassConstAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -279,9 +281,9 @@ public static function analyzeFetch( [], $stmt->class->getFirst() === "static", ); - } catch (InvalidArgumentException $_) { + } catch (InvalidArgumentException) { return true; - } catch (CircularReferenceException $e) { + } catch (CircularReferenceException) { IssueBuffer::maybeAdd( new CircularReference( 'Constant ' . $const_id . ' contains a circular reference', @@ -583,9 +585,9 @@ public static function analyzeFetch( $class_visibility, $statements_analyzer, ); - } catch (InvalidArgumentException $_) { + } catch (InvalidArgumentException) { return true; - } catch (CircularReferenceException $e) { + } catch (CircularReferenceException) { IssueBuffer::maybeAdd( new CircularReference( 'Constant ' . $const_id . ' contains a circular reference', @@ -710,7 +712,7 @@ public static function analyzeFetch( public static function analyzeAssignment( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Stmt\ClassConst $stmt, - Context $context + Context $context, ): void { assert($context->self !== null); $class_storage = $statements_analyzer->getCodebase()->classlike_storage_provider->get($context->self); @@ -754,7 +756,7 @@ public static function analyzeAssignment( public static function analyze( ClassLikeStorage $class_storage, - Codebase $codebase + Codebase $codebase, ): void { foreach ($class_storage->constants as $const_name => $const_storage) { [$parent_classlike_storage, $parent_const_storage] = self::getOverriddenConstant( @@ -852,7 +854,7 @@ private static function getOverriddenConstant( ClassLikeStorage $class_storage, ClassConstantStorage $const_storage, string $const_name, - Codebase $codebase + Codebase $codebase, ): ?array { $parent_classlike_storage = $interface_const_storage = $parent_const_storage = null; $interface_overrides = []; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php index c179d12c142..f29dbba37e1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/CloneAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); $codebase_methods = $codebase->methods; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php index 1d0eafe18fd..880342cc04f 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EmptyAnalyzer.php @@ -1,5 +1,7 @@ expr, $context); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php index 63c815ff521..88ef13bc054 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EncapsulatedStringAnalyzer.php @@ -1,9 +1,12 @@ parts as $part) { - if (ExpressionAnalyzer::analyze($statements_analyzer, $part, $context) === false) { - return false; + if ($part instanceof Expr) { + if (ExpressionAnalyzer::analyze($statements_analyzer, $part, $context) === false) { + return false; + } } - if ($part instanceof EncapsedStringPart) { + if ($part instanceof InterpolatedStringPart) { if ($literal_string !== null) { $literal_string .= $part->value; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php index 3f6b2a8d19b..093dd94e10d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/EvalAnalyzer.php @@ -1,5 +1,7 @@ inside_call; $context->inside_call = true; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php index fcafecdf3b1..17d5827e742 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ExitAnalyzer.php @@ -1,5 +1,7 @@ name)) { return '$' . $stmt->name; @@ -75,7 +77,7 @@ public static function getVarId( public static function getRootVarId( PhpParser\Node\Expr $stmt, ?string $this_class_name, - ?FileSource $source = null + ?FileSource $source = null, ): ?string { if ($stmt instanceof PhpParser\Node\Expr\Variable || $stmt instanceof PhpParser\Node\Expr\StaticPropertyFetch @@ -101,7 +103,7 @@ public static function getRootVarId( public static function getExtendedVarId( PhpParser\Node\Expr $stmt, ?string $this_class_name, - ?FileSource $source = null + ?FileSource $source = null, ): ?string { if ($stmt instanceof PhpParser\Node\Expr\Assign) { return self::getExtendedVarId($stmt->var, $this_class_name, $source); @@ -114,7 +116,7 @@ public static function getExtendedVarId( if ($root_var_id) { if ($stmt->dim instanceof PhpParser\Node\Scalar\String_ - || $stmt->dim instanceof PhpParser\Node\Scalar\LNumber + || $stmt->dim instanceof PhpParser\Node\Scalar\Int_ ) { $string_to_int = ArrayAnalyzer::getLiteralArrayKeyInt($stmt->dim->value); $offset = $string_to_int === false diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 9bc860de44d..547c212e6af 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php @@ -1,5 +1,7 @@ var, @@ -378,7 +379,7 @@ public static function taintArrayFetch( ?string $keyed_array_var_id, Union &$stmt_type, Union &$offset_type, - ?Context $context = null + ?Context $context = null, ): void { if ($statements_analyzer->data_flow_graph && ($stmt_var_type = $statements_analyzer->node_data->getType($var)) @@ -479,7 +480,7 @@ public static function getArrayAccessTypeGivenOffset( ?string $extended_var_id, Context $context, ?PhpParser\Node\Expr $assign_value = null, - ?Union $replacement_type = null + ?Union $replacement_type = null, ): Union { $offset_type = $offset_type_original->getBuilder(); @@ -509,7 +510,7 @@ public static function getArrayAccessTypeGivenOffset( if ($value_type instanceof TLiteralString) { $key_values[] = $value_type; } - } elseif ($stmt->dim instanceof PhpParser\Node\Scalar\LNumber) { + } elseif ($stmt->dim instanceof PhpParser\Node\Scalar\Int_) { $key_values[] = new TLiteralInt($stmt->dim->value); } elseif ($stmt->dim && ($stmt_dim_type = $statements_analyzer->node_data->getType($stmt->dim))) { $string_literals = $stmt_dim_type->getLiteralStrings(); @@ -585,10 +586,6 @@ public static function getArrayAccessTypeGivenOffset( $types = $array_type->getAtomicTypes(); $changed = false; foreach ($types as $type_string => $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } - $original_type_real = $type; $original_type = $type; @@ -909,7 +906,7 @@ private static function checkLiteralIntArrayOffset( ?string $extended_var_id, PhpParser\Node\Expr\ArrayDimFetch $stmt, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): void { if ($context->inside_isset || $context->inside_unset) { return; @@ -957,7 +954,7 @@ private static function checkLiteralStringArrayOffset( ?string $extended_var_id, PhpParser\Node\Expr\ArrayDimFetch $stmt, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): void { if ($context->inside_isset || $context->inside_unset) { return; @@ -1051,7 +1048,7 @@ public static function handleMixedArrayAccess( ?string $extended_var_id, PhpParser\Node\Expr\ArrayDimFetch $stmt, ?Union $array_access_type, - Atomic $type + Atomic $type, ): Union { if (!$context->collect_initializations && !$context->collect_mutations @@ -1139,7 +1136,7 @@ private static function handleArrayAccessOnArray( array &$expected_offset_types, ?Union &$array_access_type, bool &$has_array_access, - bool &$has_valid_offset + bool &$has_valid_offset, ): void { $has_array_access = true; @@ -1151,7 +1148,7 @@ private static function handleArrayAccessOnArray( $single_atomic = $key_values[0]; $from_mixed_array = $type->type_params[1]->isMixed(); - // ok, type becomes an TKeyedArray + // ok, type becomes a TKeyedArray $type = new TKeyedArray( [ $single_atomic->value => $from_mixed_array ? Type::getMixed() : Type::getNever(), @@ -1270,7 +1267,7 @@ private static function handleArrayAccessOnTArray( array &$expected_offset_types, ?Union &$array_access_type, Atomic $original_type, - bool &$has_valid_offset + bool &$has_valid_offset, ): void { // if we're assigning to an empty array with a key offset, refashion that array if ($in_assignment) { @@ -1434,7 +1431,7 @@ private static function handleArrayAccessOnClassStringMap( TClassStringMap &$type, MutableUnion $offset_type, ?Union $replacement_type, - ?Union &$array_access_type + ?Union &$array_access_type, ): void { $offset_type_parts = array_values($offset_type->getAtomicTypes()); @@ -1544,7 +1541,7 @@ private static function handleArrayAccessOnKeyedArray( TKeyedArray &$type, bool $hasMixed, array &$expected_offset_types, - bool &$has_valid_offset + bool &$has_valid_offset, ): void { $generic_key_type = $type->getGenericKeyType(); @@ -1772,7 +1769,7 @@ private static function handleArrayAccessOnNamedObject( bool $in_assignment, ?PhpParser\Node\Expr $assign_value, ?Union &$array_access_type, - bool &$has_array_access + bool &$has_array_access, ): void { $codebase = $statements_analyzer->getCodebase(); if (strtolower($type->value) === 'simplexmlelement' @@ -1925,7 +1922,7 @@ private static function handleArrayAccessOnString( MutableUnion $offset_type, array &$expected_offset_types, ?Union &$array_access_type, - bool &$has_valid_offset + bool &$has_valid_offset, ): void { if ($in_assignment && $replacement_type) { if ($replacement_type->hasMixed()) { @@ -2008,7 +2005,7 @@ private static function handleArrayAccessOnString( private static function checkArrayOffsetType( MutableUnion $offset_type, array $offset_types, - Codebase $codebase + Codebase $codebase, ): bool { $has_valid_absolute_offset = false; foreach ($offset_types as $atomic_offset_type) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php index 6ad6983b76e..3448ca749aa 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/AtomicPropertyFetchAnalyzer.php @@ -1,5 +1,7 @@ classlike_storage_provider->get($mixin->value); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $new_class_storage = null; } @@ -548,7 +547,7 @@ public static function checkPropertyDeprecation( string $prop_name, string $declaring_property_class, PhpParser\Node\Expr $stmt, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): void { $property_id = $declaring_property_class . '::$' . $prop_name; $codebase = $statements_analyzer->getCodebase(); @@ -589,7 +588,7 @@ private static function propertyFetchCanBeAnalyzed( ?string $declaring_property_class, ClassLikeStorage $class_storage, MethodIdentifier $get_method_id, - bool $in_assignment + bool $in_assignment, ): bool { if ((!$naive_property_exists || ($stmt_var_id !== '$this' @@ -743,7 +742,7 @@ public static function localizePropertyType( Union $class_property_type, TGenericObject $lhs_type_part, ClassLikeStorage $property_class_storage, - ClassLikeStorage $property_declaring_class_storage + ClassLikeStorage $property_declaring_class_storage, ): Union { $template_types = CallAnalyzer::getTemplateTypesForCall( $codebase, @@ -817,7 +816,7 @@ public static function processTaints( string $property_id, ClassLikeStorage $class_storage, bool $in_assignment, - ?Context $context = null + ?Context $context = null, ): void { if (!$statements_analyzer->data_flow_graph) { return; @@ -924,7 +923,7 @@ public static function processUnspecialTaints( string $property_id, bool $in_assignment, ?array $added_taints, - ?array $removed_taints + ?array $removed_taints, ): void { if (!$statements_analyzer->data_flow_graph) { return; @@ -981,7 +980,7 @@ private static function handleEnumName( StatementsAnalyzer $statements_analyzer, PropertyFetch $stmt, Union $stmt_var_type, - ClassLikeStorage $class_storage + ClassLikeStorage $class_storage, ): void { $relevant_enum_cases = array_filter( $stmt_var_type->getAtomicTypes(), @@ -1011,7 +1010,7 @@ private static function handleEnumValue( StatementsAnalyzer $statements_analyzer, PropertyFetch $stmt, Union $stmt_var_type, - ClassLikeStorage $class_storage + ClassLikeStorage $class_storage, ): void { $relevant_enum_cases = array_filter( $stmt_var_type->getAtomicTypes(), @@ -1036,14 +1035,7 @@ private static function handleEnumValue( foreach ($enum_cases as $enum_case) { $case_value = $enum_case->getValue($statements_analyzer->getCodebase()->classlikes); - if (is_string($case_value)) { - $case_values[] = Type::getAtomicStringFromLiteral($case_value); - } elseif (is_int($case_value)) { - $case_values[] = new TLiteralInt($case_value); - } else { - // this should never happen - $case_values[] = new TMixed(); - } + $case_values[] = $case_value ?? new TMixed(); } /** @psalm-suppress ArgumentTypeCoercion */ @@ -1060,7 +1052,7 @@ private static function handleUndefinedProperty( ?string $stmt_var_id, string $property_id, bool $has_magic_getter, - ?string $var_id + ?string $var_id, ): void { if ($context->inside_isset || $context->collect_initializations) { if ($context->pure) { @@ -1134,7 +1126,7 @@ private static function handleNonExistentClass( bool &$class_exists, bool &$interface_exists, string &$fq_class_name, - bool &$override_property_visibility + bool &$override_property_visibility, ): void { if ($codebase->interfaceExists($lhs_type_part->value)) { $interface_exists = true; @@ -1221,7 +1213,7 @@ private static function handleNonExistentProperty( ?string $stmt_var_id, bool $has_magic_getter, ?string $var_id, - bool &$has_valid_fetch_type + bool &$has_valid_fetch_type, ): void { if (($config->use_phpdoc_property_without_magic_or_parent || $class_storage->hasAttributeIncludingParents('AllowDynamicProperties', $codebase)) @@ -1288,7 +1280,7 @@ private static function getClassPropertyType( string $property_id, string $fq_class_name, string $prop_name, - TNamedObject $lhs_type_part + TNamedObject $lhs_type_part, ): Union { $class_property_type = $codebase->properties->getPropertyType( $property_id, diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php index db382c4f914..cce8934324e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ConstFetchAnalyzer.php @@ -1,5 +1,7 @@ name->toString(); @@ -103,7 +105,7 @@ public static function analyze( public static function getGlobalConstType( Codebase $codebase, string $fq_const_name, - string $const_name + string $const_name, ): ?Union { if ($const_name === 'STDERR' || $const_name === 'STDOUT' @@ -196,7 +198,7 @@ public static function getConstType( StatementsAnalyzer $statements_analyzer, string $const_name, bool $is_fully_qualified, - ?Context $context + ?Context $context, ): ?Union { $aliased_constants = $statements_analyzer->getAliases()->constants; @@ -253,7 +255,7 @@ public static function setConstType( StatementsAnalyzer $statements_analyzer, string $const_name, Union $const_type, - Context $context + Context $context, ): void { $context->vars_in_scope[$const_name] = $const_type; $context->constants[$const_name] = $const_type; @@ -269,7 +271,7 @@ public static function getConstName( PhpParser\Node\Expr $first_arg_value, NodeDataProvider $type_provider, Codebase $codebase, - Aliases $aliases + Aliases $aliases, ): ?string { $const_name = null; @@ -293,7 +295,7 @@ public static function getConstName( public static function analyzeConstAssignment( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Stmt\Const_ $stmt, - Context $context + Context $context, ): void { foreach ($stmt->consts as $const) { ExpressionAnalyzer::analyze($statements_analyzer, $const->value, $context); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php index acf004490c1..1502fca7282 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/InstancePropertyFetchAnalyzer.php @@ -1,5 +1,7 @@ inside_general_use; $context->inside_general_use = true; @@ -311,7 +313,7 @@ private static function handleScopedProperty( PhpParser\Node\Expr\PropertyFetch $stmt, Codebase $codebase, ?string $stmt_var_id, - bool $in_assignment + bool $in_assignment, ): void { $stmt_type = $context->vars_in_scope[$var_id]; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php index 790b36b30e7..bc20fc4974e 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/StaticPropertyFetchAnalyzer.php @@ -1,5 +1,7 @@ class instanceof PhpParser\Node\Name) { self::analyzeVariableStaticPropertyFetch($statements_analyzer, $stmt->class, $stmt, $context); @@ -431,7 +433,7 @@ private static function analyzeVariableStaticPropertyFetch( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $stmt_class, PhpParser\Node\Expr\StaticPropertyFetch $stmt, - Context $context + Context $context, ): void { $was_inside_general_use = $context->inside_general_use; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php index 324dd7b30b0..b7766aa2617 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/VariableFetchAnalyzer.php @@ -1,5 +1,7 @@ getFileAnalyzer()->project_analyzer; $codebase = $statements_analyzer->getCodebase(); @@ -431,7 +433,7 @@ private static function addDataFlowToVariable( PhpParser\Node\Expr\Variable $stmt, string $var_name, Union &$stmt_type, - Context $context + Context $context, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -505,7 +507,7 @@ private static function taintVariable( StatementsAnalyzer $statements_analyzer, string $var_name, Union &$type, - PhpParser\Node\Expr\Variable $stmt + PhpParser\Node\Expr\Variable $stmt, ): void { if ($statements_analyzer->data_flow_graph instanceof TaintFlowGraph && !in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) @@ -573,11 +575,7 @@ public static function getGlobalType(string $var_id, int $codebase_analysis_php_ $var_id = '$_FILES full path'; } - if (isset(self::$globalCache[$var_id])) { - return self::$globalCache[$var_id]; - } - - return Type::getMixed(); + return self::$globalCache[$var_id] ?? Type::getMixed(); } /** @@ -638,7 +636,7 @@ private static function getGlobalTypeInner(string $var_id, bool $files_full_path return new Union([$type]); } - if (in_array($var_id, array('$_GET', '$_POST', '$_REQUEST'), true)) { + if (in_array($var_id, ['$_GET', '$_POST', '$_REQUEST'], true)) { $array_key = new Union([new TNonEmptyString(), new TInt()]); $array = new TNonEmptyArray( [ diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php index 83079c98879..61728389918 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IncDecExpressionAnalyzer.php @@ -1,5 +1,7 @@ inside_assignment; $context->inside_assignment = true; @@ -53,7 +55,7 @@ public static function analyze( ) { $return_type = null; - $fake_right_expr = new VirtualLNumber(1, $stmt->getAttributes()); + $fake_right_expr = new VirtualInt(1, $stmt->getAttributes()); $statements_analyzer->node_data->setType($fake_right_expr, Type::getInt()); ArithmeticOpAnalyzer::analyze( @@ -98,7 +100,7 @@ public static function analyze( ); } } else { - $fake_right_expr = new VirtualLNumber(1, $stmt->getAttributes()); + $fake_right_expr = new VirtualInt(1, $stmt->getAttributes()); $operation = $stmt instanceof PostInc || $stmt instanceof PreInc ? new VirtualPlus( diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php index 6c4a36ab2bf..ce884452f53 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IncludeAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); $config = $codebase->config; @@ -202,7 +204,7 @@ public static function analyze( $context, $global_context, ); - } catch (UnpreparedAnalysisException $e) { + } catch (UnpreparedAnalysisException) { if ($config->skip_checks_on_unresolvable_includes) { $context->check_classes = false; $context->check_variables = false; @@ -278,7 +280,7 @@ public static function getPathTo( ?NodeDataProvider $type_provider, ?StatementsAnalyzer $statements_analyzer, string $file_name, - Config $config + Config $config, ): ?string { if (Path::isRelative($file_name)) { $file_name = $config->base_dir . DIRECTORY_SEPARATOR . $file_name; @@ -330,7 +332,7 @@ public static function getPathTo( $dir_level = 1; if (isset($stmt->getArgs()[1])) { - if ($stmt->getArgs()[1]->value instanceof PhpParser\Node\Scalar\LNumber) { + if ($stmt->getArgs()[1]->value instanceof PhpParser\Node\Scalar\Int_) { $dir_level = $stmt->getArgs()[1]->value->value; } else { if ($statements_analyzer) { @@ -435,12 +437,12 @@ public static function normalizeFilePath(string $path_to_file): string $path_to_file = str_replace('/./', '/', $path_to_file); // first remove unnecessary / duplicates - $path_to_file = preg_replace('/\/[\/]+/', '/', $path_to_file); + $path_to_file = (string) preg_replace('/\/[\/]+/', '/', $path_to_file); $reduce_pattern = '/\/[^\/]+\/\.\.\//'; while (preg_match($reduce_pattern, $path_to_file)) { - $path_to_file = preg_replace($reduce_pattern, '/', $path_to_file, 1); + $path_to_file = (string) preg_replace($reduce_pattern, '/', $path_to_file, 1); } if (DIRECTORY_SEPARATOR !== '/') { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php index 6e036490587..c504be5dda2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/InstanceofAnalyzer.php @@ -1,5 +1,7 @@ inside_general_use; $context->inside_general_use = true; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php index 6303733642b..3d21d156087 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/IssetAnalyzer.php @@ -1,5 +1,7 @@ vars as $isset_var) { if ($isset_var instanceof PhpParser\Node\Expr\PropertyFetch @@ -53,7 +55,7 @@ public static function analyze( public static function analyzeIssetVar( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $stmt, - Context $context + Context $context, ): void { $context->inside_isset = true; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php index 379e3e75bf5..948147bb0cc 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MagicConstAnalyzer.php @@ -1,5 +1,7 @@ node_data->setType($stmt, Type::getIntRange(1, null)); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php index ffaa0f5b387..3d2e242748a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/MatchAnalyzer.php @@ -1,5 +1,7 @@ inside_call; @@ -316,11 +318,12 @@ public static function analyze( /** * @param non-empty-list $conds + * @param array $attributes */ private static function convertCondsToConditional( array $conds, PhpParser\Node\Expr $match_condition, - array $attributes + array $attributes, ): PhpParser\Node\Expr { if (count($conds) === 1) { return new VirtualIdentical( @@ -331,7 +334,7 @@ private static function convertCondsToConditional( } $array_items = array_map( - static fn(PhpParser\Node\Expr $cond): PhpParser\Node\Expr\ArrayItem => + static fn(PhpParser\Node\Expr $cond): PhpParser\Node\ArrayItem => new VirtualArrayItem($cond, null, false, $cond->getAttributes()), $conds, ); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php index 129c5326d16..c6bf23c4f09 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/NullsafeAnalyzer.php @@ -1,5 +1,7 @@ var instanceof PhpParser\Node\Expr\Variable) { $was_inside_general_use = $context->inside_general_use; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php index 3d1bd13fad1..25f06a23ce7 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/PrintAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php index 1b0b183aea7..f09c4a4cca0 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/SimpleTypeInferer.php @@ -1,5 +1,7 @@ value); } - if ($stmt instanceof PhpParser\Node\Scalar\LNumber) { + if ($stmt instanceof PhpParser\Node\Scalar\Int_) { return Type::getInt(false, $stmt->value); } - if ($stmt instanceof PhpParser\Node\Scalar\DNumber) { + if ($stmt instanceof PhpParser\Node\Scalar\Float_) { return Type::getFloat($stmt->value); } @@ -509,11 +510,7 @@ public static function infer( foreach ($array_type->getAtomicTypes() as $array_atomic_type) { if ($array_atomic_type instanceof TKeyedArray) { - if (isset($array_atomic_type->properties[$dim_value])) { - return $array_atomic_type->properties[$dim_value]; - } - - return null; + return $array_atomic_type->properties[$dim_value] ?? null; } } } @@ -545,7 +542,7 @@ private static function inferArrayType( Aliases $aliases, ?FileSource $file_source = null, ?array $existing_class_constants = null, - ?string $fq_classlike_name = null + ?string $fq_classlike_name = null, ): ?Union { if (count($stmt->items) === 0) { return Type::getEmptyArray(); @@ -625,11 +622,11 @@ private static function handleArrayItem( Codebase $codebase, NodeDataProvider $nodes, ArrayCreationInfo $array_creation_info, - PhpParser\Node\Expr\ArrayItem $item, + PhpParser\Node\ArrayItem $item, Aliases $aliases, ?FileSource $file_source = null, ?array $existing_class_constants = null, - ?string $fq_classlike_name = null + ?string $fq_classlike_name = null, ): bool { if ($item->unpack) { $unpacked_array_type = self::infer( @@ -729,7 +726,7 @@ private static function handleArrayItem( $array_creation_info->all_list = $array_creation_info->all_list && $item_is_list_item; if ($item->key instanceof PhpParser\Node\Scalar\String_ - || $item->key instanceof PhpParser\Node\Scalar\LNumber + || $item->key instanceof PhpParser\Node\Scalar\Int_ || !$item->key ) { if ($item_key_value !== null @@ -778,12 +775,9 @@ private static function handleArrayItem( private static function handleUnpackedArray( ArrayCreationInfo $array_creation_info, - Union $unpacked_array_type + Union $unpacked_array_type, ): bool { foreach ($unpacked_array_type->getAtomicTypes() as $unpacked_atomic_type) { - if ($unpacked_atomic_type instanceof TList) { - $unpacked_atomic_type = $unpacked_atomic_type->getKeyedArray(); - } if ($unpacked_atomic_type instanceof TKeyedArray) { foreach ($unpacked_atomic_type->properties as $key => $property_value) { if (is_string($key)) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php index 24133ac4d86..e1ee715334c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/TernaryAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -64,7 +65,7 @@ public static function analyze( $cond_referenced_var_ids = $if_conditional_scope->cond_referenced_var_ids; $assigned_in_conditional_var_ids = $if_conditional_scope->assigned_in_conditional_var_ids; - } catch (ScopeAnalysisException $e) { + } catch (ScopeAnalysisException) { return false; } @@ -154,7 +155,7 @@ static function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { try { $if_scope->negated_clauses = Algebra::negateFormula($if_clauses); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { try { $if_scope->negated_clauses = FormulaGenerator::getFormula( $cond_object_id, @@ -165,7 +166,7 @@ static function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { $codebase, false, ); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $if_scope->negated_clauses = []; } } @@ -209,10 +210,10 @@ static function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { return false; } - $context->cond_referenced_var_ids = array_merge( - $context->cond_referenced_var_ids, - $if_context->cond_referenced_var_ids, - ); + $context->cond_referenced_var_ids = [ + ...$context->cond_referenced_var_ids, + ...$if_context->cond_referenced_var_ids, + ]; } $t_else_context->clauses = Algebra::simplifyCNF( @@ -288,16 +289,16 @@ static function (Clause $c) use ($mixed_var_ids, $cond_object_id): Clause { } } - $context->vars_possibly_in_scope = array_merge( - $context->vars_possibly_in_scope, - $if_context->vars_possibly_in_scope, - $t_else_context->vars_possibly_in_scope, - ); + $context->vars_possibly_in_scope = [ + ...$context->vars_possibly_in_scope, + ...$if_context->vars_possibly_in_scope, + ...$t_else_context->vars_possibly_in_scope, + ]; - $context->cond_referenced_var_ids = array_merge( - $context->cond_referenced_var_ids, - $t_else_context->cond_referenced_var_ids, - ); + $context->cond_referenced_var_ids = [ + ...$context->cond_referenced_var_ids, + ...$t_else_context->cond_referenced_var_ids, + ]; $lhs_type = null; $stmt_cond_type = $statements_analyzer->node_data->getType($stmt->cond); diff --git a/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/ThrowAnalyzer.php similarity index 90% rename from src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php rename to src/Psalm/Internal/Analyzer/Statements/Expression/ThrowAnalyzer.php index 6ae148f8b9e..2d0c0a07653 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ThrowAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/ThrowAnalyzer.php @@ -1,10 +1,13 @@ inside_throw = true; if (ExpressionAnalyzer::analyze($statements_analyzer, $stmt->expr, $context) === false) { @@ -85,9 +85,7 @@ public static function analyze( } } - if ($stmt instanceof PhpParser\Node\Expr\Throw_) { - $statements_analyzer->node_data->setType($stmt, Type::getNever()); - } + $statements_analyzer->node_data->setType($stmt, Type::getNever()); return true; } diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php index eec166064d2..e5d6becf49d 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/UnaryPlusMinusAnalyzer.php @@ -1,5 +1,7 @@ expr, $context) === false) { return false; @@ -113,7 +115,7 @@ private static function addDataFlow( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $stmt, PhpParser\Node\Expr $value, - string $type + string $type, ): void { $result_type = $statements_analyzer->node_data->getType($stmt); if ($statements_analyzer->data_flow_graph instanceof VariableUseGraph && $result_type) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php index a34deaedfbb..c76f52b77c4 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldAnalyzer.php @@ -1,5 +1,7 @@ getDocComment(); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php index d77e1936c99..765f13bd2dd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/YieldFromAnalyzer.php @@ -1,5 +1,7 @@ inside_call; diff --git a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php index c19c8df51a6..3d714fed28a 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -1,5 +1,7 @@ getAtomicTypes()) > 1) { $has_truthy_or_falsy_exclusive_type = false; @@ -182,9 +184,9 @@ private static function handleExpression( Context $context, bool $array_assignment, ?Context $global_context, - bool $from_stmt, + ?PhpParser\Node\Stmt $from_stmt, ?TemplateResult $template_result = null, - bool $assigned_to_reference = false + bool $assigned_to_reference = false, ): bool { if ($stmt instanceof PhpParser\Node\Expr\Variable) { return VariableFetchAnalyzer::analyze( @@ -227,23 +229,19 @@ private static function handleExpression( return true; } - if ($stmt instanceof PhpParser\Node\Scalar\EncapsedStringPart) { - return true; - } - if ($stmt instanceof PhpParser\Node\Scalar\MagicConst) { MagicConstAnalyzer::analyze($statements_analyzer, $stmt, $context); return true; } - if ($stmt instanceof PhpParser\Node\Scalar\LNumber) { + if ($stmt instanceof PhpParser\Node\Scalar\Int_) { $statements_analyzer->node_data->setType($stmt, Type::getInt(false, $stmt->value)); return true; } - if ($stmt instanceof PhpParser\Node\Scalar\DNumber) { + if ($stmt instanceof PhpParser\Node\Scalar\Float_) { $statements_analyzer->node_data->setType($stmt, Type::getFloat($stmt->value)); return true; @@ -292,7 +290,7 @@ private static function handleExpression( $stmt, $context, 0, - $from_stmt, + $from_stmt !== null, ); } @@ -312,7 +310,7 @@ private static function handleExpression( return ArrayAnalyzer::analyze($statements_analyzer, $stmt, $context); } - if ($stmt instanceof PhpParser\Node\Scalar\Encapsed) { + if ($stmt instanceof PhpParser\Node\Scalar\InterpolatedString) { return EncapsulatedStringAnalyzer::analyze($statements_analyzer, $stmt, $context); } @@ -379,7 +377,7 @@ private static function handleExpression( } if ($stmt instanceof PhpParser\Node\Expr\AssignRef) { - if (!AssignmentAnalyzer::analyzeAssignmentRef($statements_analyzer, $stmt, $context)) { + if (!AssignmentAnalyzer::analyzeAssignmentRef($statements_analyzer, $stmt, $context, $from_stmt)) { IssueBuffer::maybeAdd( new UnsupportedReferenceUsage( "This reference cannot be analyzed by Psalm", @@ -412,7 +410,7 @@ private static function handleExpression( } if ($stmt instanceof PhpParser\Node\Expr\ShellExec) { - $concat = new VirtualEncapsed($stmt->parts, $stmt->getAttributes()); + $concat = new VirtualInterpolatedString($stmt->parts, $stmt->getAttributes()); $virtual_call = new VirtualFuncCall(new VirtualName(['shell_exec']), [ new VirtualArg($concat), ], $stmt->getAttributes()); @@ -456,7 +454,7 @@ private static function handleExpression( return MatchAnalyzer::analyze($statements_analyzer, $stmt, $context); } - if ($stmt instanceof PhpParser\Node\Expr\Throw_ && $analysis_php_version_id >= 8_00_00) { + if ($stmt instanceof PhpParser\Node\Expr\Throw_) { return ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context); } @@ -474,7 +472,7 @@ private static function handleExpression( IssueBuffer::maybeAdd( new UnrecognizedExpression( - 'Psalm does not understand ' . get_class($stmt) . ' for PHP ' . + 'Psalm does not understand ' . $stmt::class . ' for PHP ' . $codebase->getMajorAnalysisPhpVersion() . '.' . $codebase->getMinorAnalysisPhpVersion(), new CodeLocation($statements_analyzer->getSource(), $stmt), ), @@ -496,7 +494,7 @@ private static function analyzeAssignment( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $stmt, Context $context, - bool $from_stmt + ?PhpParser\Node\Stmt $from_stmt, ): bool { $assignment_type = AssignmentAnalyzer::analyze( $statements_analyzer, @@ -504,12 +502,12 @@ private static function analyzeAssignment( $stmt->expr, null, $context, - $stmt->getDocComment(), + $stmt->getDocComment() ?? $from_stmt?->getDocComment(), [], !$from_stmt ? $stmt : null, ); - if ($assignment_type === false) { + if ($assignment_type === null) { return false; } @@ -523,7 +521,7 @@ private static function analyzeAssignment( private static function dispatchBeforeExpressionAnalysis( PhpParser\Node\Expr $expr, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): ?bool { $codebase = $statements_analyzer->getCodebase(); @@ -551,7 +549,7 @@ private static function dispatchBeforeExpressionAnalysis( private static function dispatchAfterExpressionAnalysis( PhpParser\Node\Expr $expr, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): ?bool { $codebase = $statements_analyzer->getCodebase(); diff --git a/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php index d7ec4dcf4b0..bcb7f693304 100644 --- a/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/GlobalAnalyzer.php @@ -1,5 +1,7 @@ collect_initializations && !$global_context) { IssueBuffer::maybeAdd( diff --git a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php index 49160c4bf8e..ec109d6b84c 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php @@ -1,5 +1,7 @@ getDocComment(); @@ -227,6 +230,23 @@ public static function analyze( $storage = $source->getFunctionLikeStorage($statements_analyzer); + if ($storage->signature_return_type + && $storage->signature_return_type->by_ref + && $stmt->expr !== null + && !($stmt->expr instanceof PhpParser\Node\Expr\Variable + || $stmt->expr instanceof PhpParser\Node\Expr\PropertyFetch + || $stmt->expr instanceof PhpParser\Node\Expr\StaticPropertyFetch + ) + ) { + IssueBuffer::maybeAdd( + new NonVariableReferenceReturn( + 'Only variable references should be returned by reference', + new CodeLocation($source, $stmt->expr), + ), + $statements_analyzer->getSuppressedIssues(), + ); + } + $cased_method_id = $source->getCorrectlyCasedMethodId(); if ($stmt->expr && $storage->location) { @@ -558,7 +578,7 @@ private static function handleTaints( PhpParser\Node\Stmt\Return_ $stmt, string $cased_method_id, Union $inferred_type, - FunctionLikeStorage $storage + FunctionLikeStorage $storage, ): void { if (!$statements_analyzer->data_flow_graph instanceof TaintFlowGraph || !$stmt->expr @@ -598,7 +618,7 @@ private static function handleTaints( private static function potentiallyInferTypesOnClosureFromParentReturnType( StatementsAnalyzer $statements_analyzer, PhpParser\Node\FunctionLike $expr, - Context $context + Context $context, ): void { // if not returning from inside of a function, return if (!$context->calling_method_id && !$context->calling_function_id) { @@ -663,7 +683,7 @@ private static function potentiallyInferTypesOnClosureFromParentReturnType( private static function inferInnerClosureTypeFromParent( Codebase $codebase, ?Union $return_type, - ?Union $parent_return_type + ?Union $parent_return_type, ): ?Union { if (!$parent_return_type) { return $return_type; diff --git a/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php index 9ade50098f6..1cdd6a8b8a2 100644 --- a/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/StaticAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -87,7 +89,7 @@ public static function analyze( } if ($context->check_variables) { - $context->vars_in_scope[$var_id] = $comment_type ? $comment_type : Type::getMixed(); + $context->vars_in_scope[$var_id] = $comment_type ?: Type::getMixed(); $context->vars_possibly_in_scope[$var_id] = true; $context->assigned_var_ids[$var_id] = (int) $stmt->getAttribute('startFilePos'); $statements_analyzer->byref_uses[$var_id] = true; diff --git a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php index e93b58b6329..51df9a31c27 100644 --- a/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/UnsetAnalyzer.php @@ -1,5 +1,7 @@ inside_unset = true; @@ -64,9 +65,6 @@ public static function analyze( $root_types = []; foreach ($context->vars_in_scope[$root_var_id]->getAtomicTypes() as $atomic_root_type) { - if ($atomic_root_type instanceof TList) { - $atomic_root_type = $atomic_root_type->getKeyedArray(); - } if ($atomic_root_type instanceof TKeyedArray) { $key_value = null; if ($key_type->isSingleIntLiteral()) { diff --git a/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php b/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php index a82a3fabb13..25fc340b51b 100644 --- a/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php +++ b/src/Psalm/Internal/Analyzer/Statements/UnusedAssignmentRemover.php @@ -1,5 +1,7 @@ findAssignStmt($stmts, $var_id, $original_location); [$assign_stmt, $assign_exp] = $search_result; @@ -122,7 +124,7 @@ private static function getPartialRemovalBounds( Codebase $codebase, CodeLocation $var_loc, int $end_bound, - bool $assign_ref = false + bool $assign_ref = false, ): FileManipulation { $var_start_loc= $var_loc->raw_file_start; $stmt_content = $codebase->file_provider->getContents( @@ -328,7 +330,7 @@ private function findAssignExp( PhpParser\Node\Expr $current_node, string $var_id, int $var_start_loc, - int $search_level = 1 + int $search_level = 1, ): array { if ($current_node instanceof PhpParser\Node\Expr\Assign || $current_node instanceof PhpParser\Node\Expr\AssignOp diff --git a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php index caf95b3801f..f1363d24093 100644 --- a/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/StatementsAnalyzer.php @@ -1,5 +1,7 @@ @@ -139,8 +137,6 @@ final class StatementsAnalyzer extends SourceAnalyzer private ?string $fake_this_class = null; - public NodeDataProvider $node_data; - public ?DataFlowGraph $data_flow_graph = null; /** @@ -153,12 +149,10 @@ final class StatementsAnalyzer extends SourceAnalyzer */ public array $foreach_var_locations = []; - public function __construct(SourceAnalyzer $source, NodeDataProvider $node_data) + public function __construct(protected SourceAnalyzer $source, public NodeDataProvider $node_data) { - $this->source = $source; $this->file_analyzer = $source->getFileAnalyzer(); $this->codebase = $source->getCodebase(); - $this->node_data = $node_data; if ($this->codebase->taint_flow_graph) { $this->data_flow_graph = new TaintFlowGraph(); @@ -177,7 +171,7 @@ public function analyze( array $stmts, Context $context, ?Context $global_context = null, - bool $root_scope = false + bool $root_scope = false, ): ?bool { if (!$stmts) { return null; @@ -271,7 +265,7 @@ private function hoistFunctions(array $stmts, Context $context): void try { $function_analyzer = new FunctionAnalyzer($stmt, $this->source); $this->function_analyzers[$fq_function_name] = $function_analyzer; - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { // do nothing } } @@ -284,7 +278,7 @@ private function hoistFunctions(array $stmts, Context $context): void private static function hoistConstants( StatementsAnalyzer $statements_analyzer, array $stmts, - Context $context + Context $context, ): void { $codebase = $statements_analyzer->getCodebase(); @@ -342,7 +336,7 @@ private static function analyzeStatement( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Stmt $stmt, Context $context, - ?Context $global_context + ?Context $global_context, ): ?bool { if (self::dispatchBeforeStatementAnalysis($stmt, $context, $statements_analyzer) === false) { return false; @@ -543,8 +537,6 @@ private static function analyzeStatement( UnsetAnalyzer::analyze($statements_analyzer, $stmt, $context); } elseif ($stmt instanceof PhpParser\Node\Stmt\Return_) { ReturnAnalyzer::analyze($statements_analyzer, $stmt, $context); - } elseif ($stmt instanceof PhpParser\Node\Stmt\Throw_) { - ThrowAnalyzer::analyze($statements_analyzer, $stmt, $context); } elseif ($stmt instanceof PhpParser\Node\Stmt\Switch_) { SwitchAnalyzer::analyze($statements_analyzer, $stmt, $context); } elseif ($stmt instanceof PhpParser\Node\Stmt\Break_) { @@ -566,7 +558,7 @@ private static function analyzeStatement( $context, false, $global_context, - true, + $stmt, ) === false) { return false; } @@ -587,7 +579,7 @@ private static function analyzeStatement( ); $class_analyzer->analyze(null, $global_context); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // disregard this exception, we'll likely see it elsewhere in the form // of an issue } @@ -606,7 +598,7 @@ private static function analyzeStatement( } else { if (IssueBuffer::accepts( new UnrecognizedStatement( - 'Psalm does not understand ' . get_class($stmt), + 'Psalm does not understand ' . $stmt::class, new CodeLocation($statements_analyzer->source, $stmt), ), $statements_analyzer->getSuppressedIssues(), @@ -697,6 +689,7 @@ private static function analyzeStatement( $file_storage->type_aliases, true, ); + /** @psalm-suppress InaccessibleProperty We just created this type */ $check_type->possibly_undefined = $possibly_undefined; @@ -733,7 +726,7 @@ private static function analyzeStatement( private static function dispatchAfterStatementAnalysis( PhpParser\Node\Stmt $stmt, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): ?bool { $codebase = $statements_analyzer->getCodebase(); @@ -759,7 +752,7 @@ private static function dispatchAfterStatementAnalysis( private static function dispatchBeforeStatementAnalysis( PhpParser\Node\Stmt $stmt, Context $context, - StatementsAnalyzer $statements_analyzer + StatementsAnalyzer $statements_analyzer, ): ?bool { $codebase = $statements_analyzer->getCodebase(); @@ -785,7 +778,7 @@ private static function dispatchBeforeStatementAnalysis( private function parseStatementDocblock( PhpParser\Comment\Doc $docblock, PhpParser\Node\Stmt $stmt, - Context $context + Context $context, ): void { $codebase = $this->getCodebase(); @@ -805,7 +798,7 @@ private function parseStatementDocblock( if ($this->parsed_docblock === null) { try { $this->parsed_docblock = DocComment::parsePreservingLength($docblock, true); - } catch (DocblockParseException $e) { + } catch (DocblockParseException) { // already reported above } } @@ -813,6 +806,7 @@ private function parseStatementDocblock( $comments = $this->parsed_docblock; if (isset($comments->tags['psalm-scope-this'])) { + assert(count($comments->tags['psalm-scope-this'])); $trimmed = trim(reset($comments->tags['psalm-scope-this'])); $scope_fqcn = Type::getFQCLNFromString($trimmed, $this->getAliases()); @@ -886,7 +880,7 @@ public function checkUnreferencedVars(array $stmts, Context $context): void } foreach ($this->unused_var_locations as [$var_id, $original_location]) { - if (strpos($var_id, '$_') === 0) { + if (str_starts_with($var_id, '$_')) { continue; } @@ -990,7 +984,7 @@ public function getUnusedVarLocations(): array public function registerPossiblyUndefinedVariable( string $undefined_var_id, - PhpParser\Node\Expr\Variable $stmt + PhpParser\Node\Expr\Variable $stmt, ): void { if (!$this->data_flow_graph) { return; @@ -1117,7 +1111,7 @@ public function getUncaughtThrows(Context $context): array $is_expected = true; break; } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $is_expected = true; break; } diff --git a/src/Psalm/Internal/Analyzer/TraitAnalyzer.php b/src/Psalm/Internal/Analyzer/TraitAnalyzer.php index dcb456fd75f..459726b4073 100644 --- a/src/Psalm/Internal/Analyzer/TraitAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/TraitAnalyzer.php @@ -1,5 +1,7 @@ source = $source; $this->file_analyzer = $source->getFileAnalyzer(); - $this->aliases = $source->getAliases(); $this->class = $class; $this->fq_class_name = $fq_class_name; $codebase = $source->getCodebase(); $this->storage = $codebase->classlike_storage_provider->get($fq_class_name); - $this->aliases = $aliases; } /** @psalm-mutation-free */ diff --git a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php index 1e13aa83a51..10267b7568f 100644 --- a/src/Psalm/Internal/Analyzer/TypeAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/TypeAnalyzer.php @@ -1,5 +1,7 @@ config = $config; + public function __construct( + private readonly Config $config, + ) { $this->use_igbinary = $config->use_igbinary; } - /** - * @return array|object|string|null - */ - public function getItem(string $path) + public function getItem(string $path): array|object|string|null { if (!file_exists($path)) { return null; @@ -92,13 +89,10 @@ public function deleteItem(string $path): void } } - /** - * @param array|object|string $item - */ - public function saveItem(string $path, $item): void + public function saveItem(string $path, array|object|string $item): void { if ($this->use_igbinary) { - $serialized = igbinary_serialize($item); + $serialized = (string) igbinary_serialize($item); } else { $serialized = serialize($item); } diff --git a/src/Psalm/Internal/Clause.php b/src/Psalm/Internal/Clause.php index b8a44c939ee..31c8e2804b0 100644 --- a/src/Psalm/Internal/Clause.php +++ b/src/Psalm/Internal/Clause.php @@ -1,5 +1,7 @@ */ - public array $redefined_vars = []; - public string $hash; /** @@ -84,12 +79,12 @@ final class Clause */ public function __construct( array $possibilities, - int $creating_conditional_id, + public int $creating_conditional_id, int $creating_object_id, bool $wedge = false, bool $reconcilable = true, - bool $generated = false, - array $redefined_vars = [] + public bool $generated = false, + public array $redefined_vars = [], ) { if ($wedge || !$reconcilable) { $this->hash = ($wedge ? 'w' : '') . $creating_object_id; @@ -107,15 +102,12 @@ public function __construct( /** @psalm-suppress ImpureFunctionCall */ $data = serialize($possibility_strings); - $this->hash = PHP_VERSION_ID >= 8_01_00 ? hash('xxh128', $data) : hash('md4', $data); + $this->hash = hash('xxh128', $data); } $this->possibilities = $possibilities; $this->wedge = $wedge; $this->reconcilable = $reconcilable; - $this->generated = $generated; - $this->redefined_vars = $redefined_vars; - $this->creating_conditional_id = $creating_conditional_id; $this->creating_object_id = $creating_object_id; } @@ -187,6 +179,7 @@ public function __toString(): string if (count($var_id_clauses) > 1) { $clause_strings[] = '('.implode(') || (', $var_id_clauses).')'; } else { + assert(!empty($var_id_clauses)); $clause_strings[] = reset($var_id_clauses); } } @@ -195,6 +188,8 @@ public function __toString(): string return '(' . implode(') || (', $clause_strings) . ')'; } + assert(!empty($clause_strings)); + return reset($clause_strings); } diff --git a/src/Psalm/Internal/Cli/LanguageServer.php b/src/Psalm/Internal/Cli/LanguageServer.php index 99a8521f004..da2502ceb6d 100644 --- a/src/Psalm/Internal/Cli/LanguageServer.php +++ b/src/Psalm/Internal/Cli/LanguageServer.php @@ -1,5 +1,7 @@ debug_emitted_issues = true; } - setlocale(LC_CTYPE, 'C'); if (isset($options['set-baseline'])) { @@ -421,7 +425,7 @@ private static function initOutputFormat(array $options): string private static function findDefaultOutputFormat(): string { $emulator = getenv('TERMINAL_EMULATOR'); - if (is_string($emulator) && substr($emulator, 0, 9) === 'JetBrains') { + if (is_string($emulator) && str_starts_with($emulator, 'JetBrains')) { return Report::TYPE_PHP_STORM; } @@ -453,8 +457,8 @@ private static function validateCliArguments(array $args): void { array_map( static function (string $arg): void { - if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); + if (str_starts_with($arg, '--') && $arg !== '--') { + $arg_name = (string) preg_replace('/=.*$/', '', substr($arg, 2), 1); if (!in_array($arg_name, self::LONG_OPTIONS) && !in_array($arg_name . ':', self::LONG_OPTIONS) @@ -467,8 +471,8 @@ static function (string $arg): void { ); exit(1); } - } elseif (strpos($arg, '-') === 0 && $arg !== '-' && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 1)); + } elseif (str_starts_with($arg, '-') && $arg !== '-' && $arg !== '--') { + $arg_name = (string) preg_replace('/=.*$/', '', substr($arg, 1)); if (!in_array($arg_name, self::SHORT_OPTIONS) && !in_array($arg_name . ':', self::SHORT_OPTIONS) @@ -505,9 +509,9 @@ private static function generateConfig(string $current_dir, array &$args): void && $arg !== '--debug' && $arg !== '--debug-by-line' && $arg !== '--debug-emitted-issues' - && strpos($arg, '--disable-extension=') !== 0 - && strpos($arg, '--root=') !== 0 - && strpos($arg, '--r=') !== 0, + && !str_starts_with($arg, '--disable-extension=') + && !str_starts_with($arg, '--root=') + && !str_starts_with($arg, '--r='), )); $init_level = null; @@ -560,7 +564,7 @@ private static function loadConfig( string $output_format, ?ClassLoader $first_autoloader, bool $run_taint_analysis, - array $options + array $options, ): Config { $config = CliUtils::initializeConfig( $path_to_config, @@ -586,7 +590,7 @@ private static function loadConfig( return $config; } - private static function initProgress(array $options, Config $config): Progress + private static function initProgress(array $options, Config $config, bool $in_ci): Progress { $debug = array_key_exists('debug', $options) || array_key_exists('debug-by-line', $options); @@ -601,9 +605,9 @@ private static function initProgress(array $options, Config $config): Progress } else { $show_errors = !$config->error_baseline || isset($options['ignore-baseline']); if (isset($options['long-progress'])) { - $progress = new LongProgress($show_errors, $show_info); + $progress = new LongProgress($show_errors, $show_info, $in_ci); } else { - $progress = new DefaultProgress($show_errors, $show_info); + $progress = new DefaultProgress($show_errors, $show_info, $in_ci); } } // output buffered warnings @@ -644,40 +648,45 @@ private static function initProviders(array $options, Config $config, string $cu } /** - * @param array{"set-baseline": string, ...} $options + * @param array{"set-baseline": mixed, ...} $options * @return array}>> */ private static function generateBaseline( array $options, Config $config, string $current_dir, - ?string $path_to_config + ?string $path_to_config, ): array { fwrite(STDERR, 'Writing error baseline to file...' . PHP_EOL); + $error_baseline = is_string($options['set-baseline']) ? $options['set-baseline'] : + ($config->error_baseline ?? Config::DEFAULT_BASELINE_NAME); + try { $issue_baseline = ErrorBaseline::read( new FileProvider, - $options['set-baseline'], + $error_baseline, ); - } catch (ConfigException $e) { + } catch (ConfigException) { $issue_baseline = []; } ErrorBaseline::create( new FileProvider, - $options['set-baseline'], + $error_baseline, IssueBuffer::getIssuesData(), $config->include_php_versions_in_error_baseline || isset($options['include-php-versions']), ); - fwrite(STDERR, "Baseline saved to {$options['set-baseline']}."); + fwrite(STDERR, "Baseline saved to $error_baseline."); - CliUtils::updateConfigFile( - $config, - $path_to_config ?? $current_dir, - $options['set-baseline'], - ); + if ($error_baseline !== $config->error_baseline) { + CliUtils::updateConfigFile( + $config, + $path_to_config ?? $current_dir, + $error_baseline, + ); + } fwrite(STDERR, PHP_EOL); @@ -762,7 +771,7 @@ private static function autoGenerateConfig( ProjectAnalyzer $project_analyzer, string $current_dir, ?string $init_source_dir, - string $vendor_dir + string $vendor_dir, ): void { $issues_by_file = IssueBuffer::getIssuesData(); @@ -804,7 +813,7 @@ private static function initStdoutReportOptions( array $options, bool $show_info, string $output_format, - bool $in_ci + bool $in_ci, ): ReportOptions { $stdout_report_options = new ReportOptions(); $stdout_report_options->use_color = !array_key_exists('m', $options); @@ -821,8 +830,7 @@ private static function initStdoutReportOptions( return $stdout_report_options; } - /** @return never */ - private static function clearGlobalCache(Config $config): void + private static function clearGlobalCache(Config $config): never { $cache_directory = $config->getGlobalCacheDirectory(); @@ -834,8 +842,7 @@ private static function clearGlobalCache(Config $config): void exit; } - /** @return never */ - private static function clearCache(Config $config): void + private static function clearCache(Config $config): never { $cache_directory = $config->getCacheDirectory(); @@ -910,13 +917,43 @@ private static function restart(array $options, int $threads, Progress $progress 'blackfire', ]); + $skipJit = defined('PHP_WINDOWS_VERSION_MAJOR') && PHP_VERSION_ID < PsalmRestarter::MIN_PHP_VERSION_WINDOWS_JIT; + if ($skipJit) { + $ini_handler->disableExtensions(['opcache', 'Zend OPcache']); + } + // If Xdebug is enabled, restart without it $ini_handler->check(); - if (!function_exists('opcache_get_status')) { - $progress->write(PHP_EOL - . 'Install the opcache extension to make use of JIT on PHP 8.0+ for a 20%+ performance boost!' - . PHP_EOL . PHP_EOL); + $hasJit = false; + if (function_exists('opcache_get_status')) { + if (true === (opcache_get_status()['jit']['on'] ?? false)) { + $hasJit = true; + $progress->write(PHP_EOL + . 'JIT acceleration: ON' + . PHP_EOL . PHP_EOL); + } else { + $progress->write(PHP_EOL + . 'JIT acceleration: OFF (an error occurred while enabling JIT)' . PHP_EOL + . 'Please report this to https://github.com/vimeo/psalm with your OS and PHP configuration!' + . PHP_EOL . PHP_EOL); + } + } else { + if ($skipJit) { + $progress->write(PHP_EOL + . 'JIT acceleration: OFF (disabled on Windows and PHP < 8.4)' . PHP_EOL + . 'Install PHP 8.4+ to make use of JIT on Windows for a 20%+ performance boost!' + . PHP_EOL . PHP_EOL); + } else { + $progress->write(PHP_EOL + . 'JIT acceleration: OFF (opcache not installed or not enabled)' . PHP_EOL + . 'Install and enable the opcache extension to make use of JIT for a 20%+ performance boost!' + . PHP_EOL . PHP_EOL); + } + } + if (isset($options['force-jit']) && !$hasJit) { + $progress->write('Exiting because JIT was requested but is not available.' . PHP_EOL . PHP_EOL); + exit(1); } } @@ -999,7 +1036,7 @@ private static function initConfig( ?string $path_to_config, string $output_format, bool $run_taint_analysis, - array $options + array $options, ): array { $init_source_dir = null; if (isset($options['i'])) { @@ -1034,11 +1071,11 @@ private static function initBaseline( Config $config, string $current_dir, ?string $path_to_config, - ?array $paths_to_check + ?array $paths_to_check, ): array { $issue_baseline = []; - if (isset($options['set-baseline']) && is_string($options['set-baseline'])) { + if (isset($options['set-baseline'])) { if ($paths_to_check !== null) { fwrite(STDERR, PHP_EOL . 'Cannot generate baseline when checking specific files' . PHP_EOL); exit(1); @@ -1110,7 +1147,7 @@ private static function storeFlowGraph(array $options, ProjectAnalyzer $project_ } /** @return false|'always'|'auto' */ - private static function shouldFindUnusedCode(array $options, Config $config) + private static function shouldFindUnusedCode(array $options, Config $config): bool|string { $find_unused_code = false; if (isset($options['find-dead-code'])) { @@ -1138,17 +1175,16 @@ private static function shouldRunTaintAnalysis(array $options): bool } /** - * @param string|bool|null $find_references_to * @param false|'always'|'auto' $find_unused_code */ private static function configureProjectAnalyzer( array $options, Config $config, ProjectAnalyzer $project_analyzer, - $find_references_to, - $find_unused_code, + string|bool|null $find_references_to, + false|string $find_unused_code, bool $find_unused_variables, - bool $run_taint_analysis + bool $run_taint_analysis, ): void { if (isset($options['generate-json-map']) && is_string($options['generate-json-map'])) { $project_analyzer->getCodebase()->store_node_types = true; @@ -1186,16 +1222,6 @@ private static function configureProjectAnalyzer( private static function configureShepherd(Config $config, array $options, array &$plugins): void { - if (is_string(getenv('PSALM_SHEPHERD_HOST'))) { // remove this block in Psalm 6 - fwrite( - STDERR, - 'Warning: PSALM_SHEPHERD_HOST env variable will be removed in Psalm 6.' - .' Please use "--shepherd" cli option or PSALM_SHEPHERD env variable' - .' to specify a custom Shepherd host/endpoint.' - . PHP_EOL, - ); - } - $is_shepherd_enabled = isset($options['shepherd']) || getenv('PSALM_SHEPHERD'); if (! $is_shepherd_enabled) { return; @@ -1210,31 +1236,16 @@ private static function configureShepherd(Config $config, array $options, array $custom_shepherd_endpoint = 'https://' . $custom_shepherd_endpoint; } - /** @psalm-suppress DeprecatedProperty */ - $config->shepherd_host = str_replace('/hooks/psalm', '', $custom_shepherd_endpoint); $config->shepherd_endpoint = $custom_shepherd_endpoint; return; } - - // Legacy part, will be removed in Psalm 6 - $custom_shepherd_host = getenv('PSALM_SHEPHERD_HOST'); - - if (is_string($custom_shepherd_host)) { - if (parse_url($custom_shepherd_host, PHP_URL_SCHEME) === null) { - $custom_shepherd_host = 'https://' . $custom_shepherd_host; - } - - /** @psalm-suppress DeprecatedProperty */ - $config->shepherd_host = $custom_shepherd_host; - $config->shepherd_endpoint = $custom_shepherd_host . '/hooks/psalm'; - } } private static function generateStubs( array $options, Providers $providers, - ProjectAnalyzer $project_analyzer + ProjectAnalyzer $project_analyzer, ): void { if (isset($options['generate-stubs']) && is_string($options['generate-stubs'])) { $stubs_location = $options['generate-stubs']; @@ -1258,7 +1269,7 @@ private static function getHelpText(): string $formats = []; /** @var string $value */ foreach ((new ReflectionClass(Report::class))->getConstants() as $constant => $value) { - if (strpos($constant, 'TYPE_') === 0) { + if (str_starts_with($constant, 'TYPE_')) { $formats[] = $value; } } @@ -1270,6 +1281,7 @@ private static function getHelpText(): string sort($reports); $reportFormats = wordwrap('"' . implode('", "', $reports) . '"', 75, "\n "); + // phpcs:disable Generic.Files.LineLength.TooLong return << $_) { // MissingParamType requires the scanning of all files to inform possible params - if (strpos($issue_name, 'Unused') !== false + if (str_contains($issue_name, 'Unused') || $issue_name === 'MissingParamType' || $issue_name === 'UnnecessaryVarAnnotation' || $issue_name === 'all' @@ -461,8 +469,8 @@ private static function validateCliArguments(array $args): void { array_map( static function (string $arg): void { - if (strpos($arg, '--') === 0 && $arg !== '--') { - $arg_name = preg_replace('/=.*$/', '', substr($arg, 2), 1); + if (str_starts_with($arg, '--') && $arg !== '--') { + $arg_name = (string) preg_replace('/=.*$/', '', substr($arg, 2), 1); if ($arg_name === 'alter') { // valid option for psalm, ignored by psalter @@ -513,17 +521,18 @@ private static function syncShortOptions(array &$options): void private static function loadCodeowners(Providers $providers): array { if (file_exists('CODEOWNERS')) { - $codeowners_file_path = realpath('CODEOWNERS'); + $codeowners_file_path = (string) realpath('CODEOWNERS'); } elseif (file_exists('.github/CODEOWNERS')) { - $codeowners_file_path = realpath('.github/CODEOWNERS'); + $codeowners_file_path = (string) realpath('.github/CODEOWNERS'); } elseif (file_exists('docs/CODEOWNERS')) { - $codeowners_file_path = realpath('docs/CODEOWNERS'); + $codeowners_file_path = (string) realpath('docs/CODEOWNERS'); } else { fwrite(STDERR, 'Cannot use --codeowner without a CODEOWNERS file' . PHP_EOL); exit(1); } $codeowners_file = file_get_contents($codeowners_file_path); + assert($codeowners_file != false); $codeowner_lines = array_map( static function (string $line): array { @@ -542,7 +551,7 @@ static function (string $line): bool { // currently we don’t match wildcard files or files that could appear anywhere // in the repo - return $line && $line[0] === '/' && strpos($line, '*') === false; + return $line && $line[0] === '/' && !str_contains($line, '*'); }, ), ); diff --git a/src/Psalm/Internal/Cli/Refactor.php b/src/Psalm/Internal/Cli/Refactor.php index 9ffc04d943a..ca4c057644c 100644 --- a/src/Psalm/Internal/Cli/Refactor.php +++ b/src/Psalm/Internal/Cli/Refactor.php @@ -1,5 +1,7 @@ |null */ - public static function getPathsToCheck($f_paths): ?array + public static function getPathsToCheck(string|array|false|null $f_paths): ?array { $paths_to_check = []; @@ -281,14 +286,10 @@ public static function getPathsToCheck($f_paths): ?array continue; } - if (strpos($input_path, '--') === 0 && strlen($input_path) > 2) { + if (str_starts_with($input_path, '--') && strlen($input_path) > 2) { // ignore --config psalm.xml // ignore common phpunit args that accept a class instead of a path, as this can cause issues on Windows - $ignored_arguments = array( - 'config', - 'printer', - 'root', - ); + $ignored_arguments = ['config', 'printer', 'root']; if (in_array(substr($input_path, 2), $ignored_arguments, true)) { ++$i; @@ -346,7 +347,7 @@ public static function initializeConfig( string $current_dir, string $output_format, ?ClassLoader $first_autoloader, - bool $create_if_non_existent = false + bool $create_if_non_existent = false, ): Config { try { if ($path_to_config) { @@ -407,9 +408,10 @@ public static function updateConfigFile(Config $config, string $config_file_path } $config_file_contents = file_get_contents($config_file); + assert($config_file_contents !== false); if ($config->error_baseline) { - $amended_config_file_contents = preg_replace( + $amended_config_file_contents = (string) preg_replace( '/errorBaseline=".*?"/', "errorBaseline=\"{$baseline_path}\"", $config_file_contents, diff --git a/src/Psalm/Internal/Codebase/Analyzer.php b/src/Psalm/Internal/Codebase/Analyzer.php index 6921b8b3af7..33c48358d9e 100644 --- a/src/Psalm/Internal/Codebase/Analyzer.php +++ b/src/Psalm/Internal/Codebase/Analyzer.php @@ -1,8 +1,9 @@ >, * function_docblock_manipulators: array>, * mutable_classes: array, + * issue_handlers: array{type: string, index: int, count: int}[], * } */ @@ -97,14 +101,6 @@ */ final class Analyzer { - private Config $config; - - private FileProvider $file_provider; - - private FileStorageProvider $file_storage_provider; - - private Progress $progress; - /** * Used to store counts of mixed vs non-mixed variables * @@ -186,15 +182,11 @@ final class Analyzer public array $mutable_classes = []; public function __construct( - Config $config, - FileProvider $file_provider, - FileStorageProvider $file_storage_provider, - Progress $progress + private readonly Config $config, + private readonly FileProvider $file_provider, + private readonly FileStorageProvider $file_storage_provider, + private readonly Progress $progress, ) { - $this->config = $config; - $this->file_provider = $file_provider; - $this->file_storage_provider = $file_storage_provider; - $this->progress = $progress; } /** @@ -233,7 +225,7 @@ public function canReportIssues(string $file_path): bool private function getFileAnalyzer( ProjectAnalyzer $project_analyzer, string $file_path, - array $filetype_analyzers + array $filetype_analyzers, ): FileAnalyzer { $extension = pathinfo($file_path, PATHINFO_EXTENSION); @@ -254,7 +246,7 @@ public function analyzeFiles( ProjectAnalyzer $project_analyzer, int $pool_size, bool $alter_code, - bool $consolidate_analyzed_data = false + bool $consolidate_analyzed_data = false, ): void { $this->loadCachedResults($project_analyzer); @@ -266,7 +258,7 @@ public function analyzeFiles( $this->files_to_analyze = array_filter( $this->files_to_analyze, - [$this->file_provider, 'fileExists'], + $this->file_provider->fileExists(...), ); $this->doAnalysis($project_analyzer, $pool_size); @@ -329,9 +321,9 @@ private function doAnalysis(ProjectAnalyzer $project_analyzer, int $pool_size): $codebase = $project_analyzer->getCodebase(); - $analysis_worker = Closure::fromCallable([$this, 'analysisWorker']); + $analysis_worker = $this->analysisWorker(...); - $task_done_closure = Closure::fromCallable([$this, 'taskDoneClosure']); + $task_done_closure = $this->taskDoneClosure(...); if ($pool_size > 1 && count($this->files_to_analyze) > $pool_size) { $shuffle_count = $pool_size + 1; @@ -393,7 +385,7 @@ static function (): void { $file_reference_provider->setMethodParamUses([]); }, $analysis_worker, - Closure::fromCallable([$this, 'getWorkerData']), + $this->getWorkerData(...), $task_done_closure, ); @@ -416,6 +408,10 @@ static function (): void { IssueBuffer::addUsedSuppressions($pool_data['used_suppressions']); } + if ($codebase->config->find_unused_issue_handler_suppression) { + $codebase->config->combineIssueHandlerSuppressions($pool_data['issue_handlers']); + } + if ($codebase->taint_flow_graph && $pool_data['taint_data']) { $codebase->taint_flow_graph->addGraph($pool_data['taint_data']); } @@ -596,7 +592,7 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer): void [$base_class, $trait] = explode('&', $changed_member); foreach ($all_referencing_methods as $member_id => $_) { - if (strpos($member_id, $base_class . '::') !== 0) { + if (!str_starts_with($member_id, $base_class . '::')) { continue; } @@ -618,7 +614,7 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer): void // also check for things that might invalidate constructor property initialisation if (isset($all_referencing_methods[$unchanged_signature_member_id])) { foreach ($all_referencing_methods[$unchanged_signature_member_id] as $referencing_method_id => $_) { - if (substr($referencing_method_id, -13) === '::__construct') { + if (str_ends_with($referencing_method_id, '::__construct')) { $referencing_base_classlike = explode('::', $referencing_method_id)[0]; $unchanged_signature_classlike = explode('::', $unchanged_signature_member_id)[0]; @@ -629,7 +625,7 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer): void $referencing_storage = $codebase->classlike_storage_provider->get( $referencing_base_classlike, ); - } catch (InvalidArgumentException $_) { + } catch (InvalidArgumentException) { // Workaround for #3671 $newly_invalidated_methods[$referencing_method_id] = true; $referencing_storage = null; @@ -672,7 +668,7 @@ public function loadCachedResults(ProjectAnalyzer $project_analyzer): void $method_param_uses[$member_id], ); - $member_stub = preg_replace('/::.*$/', '::*', $member_id, 1); + $member_stub = (string) preg_replace('/::.*$/', '::*', $member_id, 1); if (isset($all_referencing_methods[$member_stub])) { $newly_invalidated_methods = array_merge( @@ -1184,7 +1180,7 @@ public function addNodeType( string $file_path, PhpParser\Node $node, string $node_type, - ?PhpParser\Node $parent_node = null + ?PhpParser\Node $parent_node = null, ): void { if ($node_type === '') { throw new UnexpectedValueException('non-empty node_type expected'); @@ -1201,7 +1197,7 @@ public function addNodeArgument( int $start_position, int $end_position, string $reference, - int $argument_number + int $argument_number, ): void { if ($reference === '') { throw new UnexpectedValueException('non-empty reference expected'); @@ -1633,6 +1629,7 @@ private function getWorkerData(): array 'used_suppressions' => $codebase->track_unused_suppressions ? IssueBuffer::getUsedSuppressions() : [], 'function_docblock_manipulators' => FunctionDocblockManipulator::getManipulators(), 'mutable_classes' => $codebase->analyzer->mutable_classes, + 'issue_handlers' => $this->config->getIssueHandlerSuppressions() ]; // @codingStandardsIgnoreEnd } diff --git a/src/Psalm/Internal/Codebase/AssertionsFromInheritanceResolver.php b/src/Psalm/Internal/Codebase/AssertionsFromInheritanceResolver.php index aa42e2a8942..594768af845 100644 --- a/src/Psalm/Internal/Codebase/AssertionsFromInheritanceResolver.php +++ b/src/Psalm/Internal/Codebase/AssertionsFromInheritanceResolver.php @@ -10,7 +10,6 @@ use Psalm\Storage\Possibilities; use function array_filter; -use function array_merge; use function array_values; use function strtolower; @@ -19,12 +18,9 @@ */ final class AssertionsFromInheritanceResolver { - private Codebase $codebase; - public function __construct( - Codebase $codebase + private readonly Codebase $codebase, ) { - $this->codebase = $codebase; } /** @@ -32,15 +28,15 @@ public function __construct( */ public function resolve( MethodStorage $method_storage, - ClassLikeStorage $called_class + ClassLikeStorage $called_class, ): array { $method_name_lc = strtolower($method_storage->cased_name ?? ''); $assertions = $method_storage->assertions; - $inherited_classes_and_interfaces = array_values(array_filter(array_merge( - $called_class->parent_classes, - $called_class->class_implements, - ), fn(string $classOrInterface) => $this->codebase->classOrInterfaceOrEnumExists($classOrInterface))); + $inherited_classes_and_interfaces = array_values(array_filter([ + ...$called_class->parent_classes, + ...$called_class->class_implements, + ], fn(string $classOrInterface) => $this->codebase->classOrInterfaceOrEnumExists($classOrInterface))); foreach ($inherited_classes_and_interfaces as $potential_assertion_providing_class) { $potential_assertion_providing_classlike_storage = $this->codebase->classlike_storage_provider->get( diff --git a/src/Psalm/Internal/Codebase/ClassConstantByWildcardResolver.php b/src/Psalm/Internal/Codebase/ClassConstantByWildcardResolver.php index 0e6ef6f8a33..edfe9717c29 100644 --- a/src/Psalm/Internal/Codebase/ClassConstantByWildcardResolver.php +++ b/src/Psalm/Internal/Codebase/ClassConstantByWildcardResolver.php @@ -15,13 +15,12 @@ */ final class ClassConstantByWildcardResolver { - private StorageByPatternResolver $resolver; - private Codebase $codebase; + private readonly StorageByPatternResolver $resolver; - public function __construct(Codebase $codebase) - { + public function __construct( + private readonly Codebase $codebase, + ) { $this->resolver = new StorageByPatternResolver(); - $this->codebase = $codebase; } /** diff --git a/src/Psalm/Internal/Codebase/ClassLikes.php b/src/Psalm/Internal/Codebase/ClassLikes.php index 5224af6507c..0f6318efc3f 100644 --- a/src/Psalm/Internal/Codebase/ClassLikes.php +++ b/src/Psalm/Internal/Codebase/ClassLikes.php @@ -1,5 +1,7 @@ */ @@ -141,25 +139,13 @@ final class ClassLikes public bool $collect_locations = false; - private StatementsProvider $statements_provider; - - private Config $config; - - private Scanner $scanner; - public function __construct( - Config $config, - ClassLikeStorageProvider $storage_provider, - FileReferenceProvider $file_reference_provider, - StatementsProvider $statements_provider, - Scanner $scanner + private readonly Config $config, + private readonly ClassLikeStorageProvider $classlike_storage_provider, + public FileReferenceProvider $file_reference_provider, + private readonly StatementsProvider $statements_provider, + private readonly Scanner $scanner, ) { - $this->config = $config; - $this->classlike_storage_provider = $storage_provider; - $this->file_reference_provider = $file_reference_provider; - $this->statements_provider = $statements_provider; - $this->scanner = $scanner; - $this->collectPredefinedClassLikes(); } @@ -169,7 +155,7 @@ private function collectPredefinedClassLikes(): void $predefined_classes = get_declared_classes(); foreach ($predefined_classes as $predefined_class) { - $predefined_class = preg_replace('/^\\\/', '', $predefined_class, 1); + $predefined_class = (string) preg_replace('/^\\\/', '', $predefined_class, 1); /** @psalm-suppress ArgumentTypeCoercion */ $reflection_class = new ReflectionClass($predefined_class); @@ -185,7 +171,7 @@ private function collectPredefinedClassLikes(): void $predefined_interfaces = get_declared_interfaces(); foreach ($predefined_interfaces as $predefined_interface) { - $predefined_interface = preg_replace('/^\\\/', '', $predefined_interface, 1); + $predefined_interface = (string) preg_replace('/^\\\/', '', $predefined_interface, 1); /** @psalm-suppress ArgumentTypeCoercion */ $reflection_class = new ReflectionClass($predefined_interface); @@ -325,7 +311,7 @@ public function hasFullyQualifiedClassName( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { $fq_class_name_lc = strtolower($this->getUnAliasedName($fq_class_name)); @@ -393,7 +379,7 @@ public function hasFullyQualifiedInterfaceName( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { $fq_class_name_lc = strtolower($this->getUnAliasedName($fq_class_name)); @@ -461,7 +447,7 @@ public function hasFullyQualifiedEnumName( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { $fq_class_name_lc = strtolower($this->getUnAliasedName($fq_class_name)); @@ -552,7 +538,7 @@ public function classOrInterfaceExists( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { return $this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id) || $this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id); @@ -565,7 +551,7 @@ public function classOrInterfaceOrEnumExists( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { return $this->classExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id) || $this->interfaceExists($fq_class_name, $code_location, $calling_fq_class_name, $calling_method_id) @@ -579,7 +565,7 @@ public function classExists( string $fq_class_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[$fq_class_name])) { return false; @@ -679,7 +665,7 @@ public function interfaceExists( string $fq_interface_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[strtolower($fq_interface_name)])) { return false; @@ -697,7 +683,7 @@ public function enumExists( string $fq_enum_name, ?CodeLocation $code_location = null, ?string $calling_fq_class_name = null, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { if (isset(ClassLikeAnalyzer::SPECIAL_TYPES[strtolower($fq_enum_name)])) { return false; @@ -849,7 +835,7 @@ public function consolidateAnalyzedData(Methods $methods, ?Progress $progress, b foreach ($this->existing_classlikes_lc as $fq_class_name_lc => $_) { try { $classlike_storage = $this->classlike_storage_provider->get($fq_class_name_lc); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -921,7 +907,7 @@ public function consolidateAnalyzedData(Methods $methods, ?Progress $progress, b public static function makeImmutable( PhpParser\Node\Stmt\Class_ $class_stmt, ProjectAnalyzer $project_analyzer, - string $file_path + string $file_path, ): void { $manipulator = ClassDocblockManipulator::getForClass( $project_analyzer, @@ -956,7 +942,7 @@ public function moveMethods(Methods $methods, ?Progress $progress = null): void $source_method_storage = $methods->getStorage( new MethodIdentifier(...$source_parts), ); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -964,7 +950,7 @@ public function moveMethods(Methods $methods, ?Progress $progress = null): void try { $classlike_storage = $this->classlike_storage_provider->get($destination_fq_class_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -1033,7 +1019,7 @@ public function moveProperties(Properties $properties, ?Progress $progress = nul foreach ($codebase->properties_to_move as $source => $destination) { try { $source_property_storage = $properties->getStorage($source); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -1197,7 +1183,7 @@ public function handleClassLikeReferenceInMigration( string $fq_class_name, ?string $calling_method_id, bool $force_change = false, - bool $was_self = false + bool $was_self = false, ): bool { if ($class_name_node instanceof VirtualNode) { return false; @@ -1379,7 +1365,7 @@ public function handleDocblockTypeInMigration( StatementsSource $source, Union $type, CodeLocation $type_location, - ?string $calling_method_id + ?string $calling_method_id, ): void { $calling_fq_class_name = $source->getFQCLN(); $fq_class_name_lc = strtolower($calling_fq_class_name ?? ''); @@ -1509,7 +1495,7 @@ public function airliftClassLikeReference( int $source_start, int $source_end, bool $add_class_constant = false, - bool $allow_self = false + bool $allow_self = false, ): void { $project_analyzer = ProjectAnalyzer::getInstance(); $codebase = $project_analyzer->getCodebase(); @@ -1545,7 +1531,7 @@ public function airliftClassDefinedDocblockType( string $destination_fq_class_name, string $source_file_path, int $source_start, - int $source_end + int $source_end, ): void { $project_analyzer = ProjectAnalyzer::getInstance(); $codebase = $project_analyzer->getCodebase(); @@ -1619,7 +1605,7 @@ public function getClassConstantType( ?StatementsAnalyzer $statements_analyzer = null, array $visited_constant_ids = [], bool $late_static_binding = false, - bool $in_value_of_context = false + bool $in_value_of_context = false, ): ?Union { $class_name = strtolower($class_name); @@ -1693,7 +1679,7 @@ private function checkMethodReferences(ClassLikeStorage $classlike_storage, Meth try { $declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -1799,7 +1785,7 @@ private function checkMethodReferences(ClassLikeStorage $classlike_storage, Meth foreach ($classlike_storage->class_implements as $fq_interface_name_lc => $_) { try { $interface_storage = $this->classlike_storage_provider->get($fq_interface_name_lc); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -1957,7 +1943,7 @@ private function checkMethodParamReferences(ClassLikeStorage $classlike_storage) try { $declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -2026,7 +2012,7 @@ private function findPossibleMethodParamTypes(ClassLikeStorage $classlike_storag try { $declaring_classlike_storage = $this->classlike_storage_provider->get($declaring_fq_classlike_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -2392,7 +2378,7 @@ public function getStorageFor(string $fq_class_name): ?ClassLikeStorage try { return $this->classlike_storage_provider->get($fq_class_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return null; } } @@ -2403,7 +2389,7 @@ private function getConstantType( int $visibility, ?StatementsAnalyzer $statements_analyzer, array $visited_constant_ids, - bool $late_static_binding + bool $late_static_binding, ): ?Union { $constant_resolver = new StorageByPatternResolver(); $resolved_constants = $constant_resolver->resolveConstants( @@ -2465,7 +2451,7 @@ private function getConstantType( private function getEnumType( ClassLikeStorage $class_like_storage, - string $constant_name + string $constant_name, ): ?Union { $constant_resolver = new StorageByPatternResolver(); $resolved_enums = $constant_resolver->resolveEnums( @@ -2487,7 +2473,7 @@ private function getEnumType( private function filterConstantNameByVisibility( ClassConstantStorage $constant_storage, - int $visibility + int $visibility, ): bool { if ($visibility === ReflectionProperty::IS_PUBLIC diff --git a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php index 6506bed61f5..73584f3c4e8 100644 --- a/src/Psalm/Internal/Codebase/ConstantTypeResolver.php +++ b/src/Psalm/Internal/Codebase/ConstantTypeResolver.php @@ -1,5 +1,7 @@ enum_cases[$c->case])) { if ($c instanceof EnumValueFetch) { $value = $enum_storage->enum_cases[$c->case]->value; - if (is_string($value)) { - return Type::getString($value)->getSingleAtomic(); - } elseif (is_int($value)) { - return Type::getInt(false, $value)->getSingleAtomic(); - } elseif ($value instanceof UnresolvedConstantComponent) { - return self::resolve( - $classlikes, - $value, - $statements_analyzer, - $visited_constant_ids + [$c_id => true], - ); + + if ($value !== null) { + if ($value instanceof UnresolvedConstantComponent) { + return self::resolve( + $classlikes, + $value, + $statements_analyzer, + $visited_constant_ids + [$c_id => true], + ); + } else { + return $value; + } } } elseif ($c instanceof EnumNameFetch) { return Type::getString($c->case)->getSingleAtomic(); @@ -364,10 +367,8 @@ public static function resolve( /** * Note: This takes an array, but any array should only contain other arrays and scalars. - * - * @param array|string|int|float|bool|null $value */ - public static function getLiteralTypeFromScalarValue($value): Atomic + public static function getLiteralTypeFromScalarValue(array|string|int|float|bool|null $value): Atomic { if (is_array($value)) { if (empty($value)) { diff --git a/src/Psalm/Internal/Codebase/DataFlowGraph.php b/src/Psalm/Internal/Codebase/DataFlowGraph.php index 6a79fa7e2bf..f94d6f74a83 100644 --- a/src/Psalm/Internal/Codebase/DataFlowGraph.php +++ b/src/Psalm/Internal/Codebase/DataFlowGraph.php @@ -1,5 +1,7 @@ id; $to_id = $to->id; @@ -63,13 +65,13 @@ public function addPath( protected static function shouldIgnoreFetch( string $path_type, string $expression_type, - array $previous_path_types + array $previous_path_types, ): bool { $el = strlen($expression_type); // arraykey-fetch requires a matching arraykey-assignment at the same level // otherwise the tainting is not valid - if (strpos($path_type, $expression_type . '-fetch-') === 0 + if (str_starts_with($path_type, $expression_type . '-fetch-') || ($path_type === 'arraykey-fetch' && $expression_type === 'arrayvalue') ) { $fetch_nesting = 0; @@ -85,11 +87,11 @@ protected static function shouldIgnoreFetch( $fetch_nesting--; } - if (strpos($previous_path_type, $expression_type . '-fetch') === 0) { + if (str_starts_with($previous_path_type, $expression_type . '-fetch')) { $fetch_nesting++; } - if (strpos($previous_path_type, $expression_type . '-assignment-') === 0) { + if (str_starts_with($previous_path_type, $expression_type . '-assignment-')) { if ($fetch_nesting > 0) { $fetch_nesting--; continue; diff --git a/src/Psalm/Internal/Codebase/Functions.php b/src/Psalm/Internal/Codebase/Functions.php index 22fe2ee8edf..07f5025cc9c 100644 --- a/src/Psalm/Internal/Codebase/Functions.php +++ b/src/Psalm/Internal/Codebase/Functions.php @@ -1,5 +1,7 @@ */ @@ -52,12 +54,10 @@ final class Functions public DynamicFunctionStorageProvider $dynamic_storage_provider; - private Reflection $reflection; - - public function __construct(FileStorageProvider $storage_provider, Reflection $reflection) - { - $this->file_storage_provider = $storage_provider; - $this->reflection = $reflection; + public function __construct( + private readonly FileStorageProvider $file_storage_provider, + private readonly Reflection $reflection, + ) { $this->return_type_provider = new FunctionReturnTypeProvider(); $this->existence_provider = new FunctionExistenceProvider(); $this->params_provider = new FunctionParamsProvider(); @@ -73,15 +73,14 @@ public function getStorage( ?StatementsAnalyzer $statements_analyzer, string $function_id, ?string $root_file_path = null, - ?string $checked_file_path = null + ?string $checked_file_path = null, ): FunctionStorage { if ($function_id[0] === '\\') { $function_id = substr($function_id, 1); } - $from_stubs = false; if (isset(self::$stubbed_functions[$function_id])) { - $from_stubs = self::$stubbed_functions[$function_id]; + return self::$stubbed_functions[$function_id]; } $file_storage = null; @@ -113,10 +112,6 @@ public function getStorage( return $this->reflection->getFunctionStorage($function_id); } - if ($from_stubs) { - return $from_stubs; - } - throw new UnexpectedValueException( 'Expecting non-empty $root_file_path and $checked_file_path', ); @@ -135,10 +130,6 @@ public function getStorage( } } - if ($from_stubs) { - return $from_stubs; - } - throw new UnexpectedValueException( 'Expecting ' . $function_id . ' to have storage in ' . $checked_file_path, ); @@ -149,10 +140,6 @@ public function getStorage( $declaring_file_storage = $this->file_storage_provider->get($declaring_file_path); if (!isset($declaring_file_storage->functions[$function_id])) { - if ($from_stubs) { - return $from_stubs; - } - throw new UnexpectedValueException( 'Not expecting ' . $function_id . ' to not have storage in ' . $declaring_file_path, ); @@ -184,7 +171,7 @@ public function getAllStubbedFunctions(): array */ public function functionExists( StatementsAnalyzer $statements_analyzer, - string $function_id + string $function_id, ): bool { if ($this->existence_provider->has($function_id)) { $function_exists = $this->existence_provider->doesFunctionExist($statements_analyzer, $function_id); @@ -249,7 +236,7 @@ public function getFullyQualifiedFunctionNameFromString(string $function_name, S $imported_function_namespaces = $aliases->functions; $imported_namespaces = $aliases->uses; - if (strpos($function_name, '\\') !== false) { + if (str_contains($function_name, '\\')) { $function_name_parts = explode('\\', $function_name); $first_namespace = array_shift($function_name_parts); $first_namespace_lcase = strtolower($first_namespace); @@ -278,7 +265,7 @@ public function getMatchingFunctionNames( string $stub, int $offset, string $file_path, - Codebase $codebase + Codebase $codebase, ): array { if ($stub[0] === '*') { $stub = substr($stub, 1); @@ -322,10 +309,10 @@ public function getMatchingFunctionNames( if ($current_namespace_aliases) { foreach ($current_namespace_aliases->functions as $alias_name => $function_name) { - if (strpos($alias_name, $stub) === 0) { + if (str_starts_with($alias_name, $stub)) { try { $match_function_patterns[] = $function_name; - } catch (Exception $e) { + } catch (Exception) { } } } @@ -346,8 +333,8 @@ public function getMatchingFunctionNames( foreach ($match_function_patterns as $pattern) { $pattern_lc = strtolower($pattern); - if (substr($pattern, -1, 1) === '*') { - if (strpos($function_name, rtrim($pattern_lc, '*')) !== 0) { + if (str_ends_with($pattern, '*')) { + if (!str_starts_with($function_name, rtrim($pattern_lc, '*'))) { continue; } } elseif ($function_name !== $pattern) { @@ -403,7 +390,7 @@ public function isCallMapFunctionPure( ?NodeTypeProvider $type_provider, string $function_id, ?array $args, - bool &$must_use = true + bool &$must_use = true, ): bool { if (ImpureFunctionsList::isImpure($function_id)) { return false; @@ -417,11 +404,11 @@ public function isCallMapFunctionPure( } } - if (strpos($function_id, 'image') === 0) { + if (str_starts_with($function_id, 'image')) { return false; } - if (strpos($function_id, 'readline') === 0) { + if (str_starts_with($function_id, 'readline')) { return false; } @@ -451,7 +438,7 @@ public function isCallMapFunctionPure( try { return $codebase->methods->getStorage($count_method_id)->mutation_free; - } catch (Exception $e) { + } catch (Exception) { // do nothing } } diff --git a/src/Psalm/Internal/Codebase/ImpureFunctionsList.php b/src/Psalm/Internal/Codebase/ImpureFunctionsList.php index 386598af477..80c1817b149 100644 --- a/src/Psalm/Internal/Codebase/ImpureFunctionsList.php +++ b/src/Psalm/Internal/Codebase/ImpureFunctionsList.php @@ -1,5 +1,7 @@ 3 && strpos($arg_name, 'rw_') === 0) { + if ($by_reference && strlen($arg_name) > 3 && str_starts_with($arg_name, 'rw_')) { $arg_name = substr($arg_name, 3); } diff --git a/src/Psalm/Internal/Codebase/Methods.php b/src/Psalm/Internal/Codebase/Methods.php index cdb067a43bd..f4c8f159f9c 100644 --- a/src/Psalm/Internal/Codebase/Methods.php +++ b/src/Psalm/Internal/Codebase/Methods.php @@ -1,5 +1,7 @@ classlike_storage_provider = $storage_provider; - $this->file_reference_provider = $file_reference_provider; - $this->classlikes = $classlikes; $this->return_type_provider = new MethodReturnTypeProvider(); $this->existence_provider = new MethodExistenceProvider(); $this->visibility_provider = new MethodVisibilityProvider(); @@ -100,7 +93,7 @@ public function methodExists( ?string $source_file_path = null, bool $use_method_existence_provider = true, bool $is_used = false, - bool $with_pseudo = false + bool $with_pseudo = false, ): bool { $fq_class_name = $method_id->fq_class_name; $method_name = $method_id->method_name; @@ -124,7 +117,7 @@ public function methodExists( try { $class_storage = $this->classlike_storage_provider->get($fq_class_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return false; } @@ -349,7 +342,7 @@ public function getMethodParams( MethodIdentifier $method_id, ?StatementsSource $source = null, ?array $args = null, - ?Context $context = null + ?Context $context = null, ): array { $fq_class_name = $method_id->fq_class_name; $method_name = $method_id->method_name; @@ -495,7 +488,7 @@ public static function localizeType( Codebase $codebase, Union $type, string $appearing_fq_class_name, - string $base_fq_class_name + string $base_fq_class_name, ): Union { $class_storage = $codebase->classlike_storage_provider->get($appearing_fq_class_name); $extends = $class_storage->template_extended_params; @@ -518,7 +511,7 @@ public static function localizeType( */ public static function getExtendedTemplatedTypes( TTemplateParam $atomic_type, - array $extends + array $extends, ): array { $extra_added_types = []; @@ -561,7 +554,7 @@ public function getMethodReturnType( ?string &$self_class, ?SourceAnalyzer $source_analyzer = null, ?array $args = null, - ?TemplateResult $template_result = null + ?TemplateResult $template_result = null, ): ?Union { $original_fq_class_name = $method_id->fq_class_name; $original_method_name = $method_id->method_name; @@ -591,6 +584,7 @@ public function getMethodReturnType( if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$original_method_name])) { $appearing_method_id = reset($class_storage->overridden_method_ids[$original_method_name]); + assert($appearing_method_id !== false); } else { return null; } @@ -635,9 +629,8 @@ public function getMethodReturnType( if (UnionTypeComparator::isContainedBy( $source_analyzer->getCodebase(), - is_int($case_value) ? - Type::getInt(false, $case_value) : - Type::getString($case_value), + // XXX: why TString? Perhaps it should be string|int? + new Union([$case_value ?? new TString()]), $first_arg_type, )) { $types[] = new TEnumCase($original_fq_class_name, $case_name); @@ -814,7 +807,7 @@ public function getMethodReturnType( $overridden_storage_return_type, $source_analyzer->getCodebase(), ); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // TODO: fix } } else { @@ -941,7 +934,7 @@ public function getMethodReturnsByRef(MethodIdentifier $method_id): bool public function getMethodReturnTypeLocation( MethodIdentifier $method_id, - ?CodeLocation &$defined_location = null + ?CodeLocation &$defined_location = null, ): ?CodeLocation { $method_id = $this->getDeclaringMethodId($method_id); @@ -981,7 +974,7 @@ public function setDeclaringMethodId( string $fq_class_name, string $method_name_lc, string $declaring_fq_class_name, - string $declaring_method_name_lc + string $declaring_method_name_lc, ): void { $class_storage = $this->classlike_storage_provider->get($fq_class_name); @@ -999,7 +992,7 @@ public function setAppearingMethodId( string $fq_class_name, string $method_name_lc, string $appearing_fq_class_name, - string $appearing_method_name_lc + string $appearing_method_name_lc, ): void { $class_storage = $this->classlike_storage_provider->get($fq_class_name); @@ -1012,7 +1005,7 @@ public function setAppearingMethodId( /** @psalm-mutation-free */ public function getDeclaringMethodId( MethodIdentifier $method_id, - bool $with_pseudo = false + bool $with_pseudo = false, ): ?MethodIdentifier { $fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name); @@ -1025,6 +1018,8 @@ public function getDeclaringMethodId( } if ($class_storage->abstract && isset($class_storage->overridden_method_ids[$method_name])) { + assert(!empty($class_storage->overridden_method_ids[$method_name])); + return reset($class_storage->overridden_method_ids[$method_name]); } @@ -1039,7 +1034,7 @@ public function getDeclaringMethodId( * Get the class this method appears in (vs is declared in, which could give a trait */ public function getAppearingMethodId( - MethodIdentifier $method_id + MethodIdentifier $method_id, ): ?MethodIdentifier { $fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name); @@ -1148,22 +1143,20 @@ public function getStorage(MethodIdentifier $method_id, bool $with_pseudo = fals } $method_name = $method_id->method_name; - - $storage = $class_storage->methods[$method_name] ?? null; - - if ($storage === null && $with_pseudo) { - $storage = $class_storage->pseudo_methods[$method_name] + $method_storage = $class_storage->methods[$method_name] ?? null; + if ($method_storage === null && $with_pseudo) { + $method_storage = $class_storage->pseudo_methods[$method_name] ?? $class_storage->pseudo_static_methods[$method_name] ?? null; } - if ($storage === null) { + if ($method_storage === null) { throw new UnexpectedValueException( '$storage should not be null for ' . $method_id, ); } - return $storage; + return $method_storage; } /** @psalm-mutation-free */ @@ -1171,7 +1164,7 @@ public function hasStorage(MethodIdentifier $method_id): bool { try { $class_storage = $this->classlike_storage_provider->get($method_id->fq_class_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return false; } diff --git a/src/Psalm/Internal/Codebase/Populator.php b/src/Psalm/Internal/Codebase/Populator.php index 864a27e7c91..f00e45fa50e 100644 --- a/src/Psalm/Internal/Codebase/Populator.php +++ b/src/Psalm/Internal/Codebase/Populator.php @@ -1,5 +1,7 @@ > */ private array $invalid_class_storages = []; - private Progress $progress; - - private ClassLikes $classlikes; - - private FileReferenceProvider $file_reference_provider; - public function __construct( - ClassLikeStorageProvider $classlike_storage_provider, - FileStorageProvider $file_storage_provider, - ClassLikes $classlikes, - FileReferenceProvider $file_reference_provider, - Progress $progress + private readonly ClassLikeStorageProvider $classlike_storage_provider, + private readonly FileStorageProvider $file_storage_provider, + private readonly ClassLikes $classlikes, + private readonly FileReferenceProvider $file_reference_provider, + private readonly Progress $progress, ) { - $this->classlike_storage_provider = $classlike_storage_provider; - $this->file_storage_provider = $file_storage_provider; - $this->classlikes = $classlikes; - $this->progress = $progress; - $this->file_reference_provider = $file_reference_provider; } public function populateCodebase(): void @@ -95,7 +82,7 @@ public function populateCodebase(): void foreach ($class_storage->dependent_classlikes as $dependent_classlike_lc => $_) { try { $dependee_storage = $this->classlike_storage_provider->get($dependent_classlike_lc); - } catch (InvalidArgumentException $exception) { + } catch (InvalidArgumentException) { continue; } @@ -273,7 +260,7 @@ private function populateClassLikeStorage(ClassLikeStorage $storage, array $depe private function populateOverriddenMethods( ClassLikeStorage $storage, - ClassLikeStorageProvider $storage_provider + ClassLikeStorageProvider $storage_provider, ): void { $interface_method_implementers = []; foreach ($storage->class_implements as $interface) { @@ -284,7 +271,7 @@ private function populateOverriddenMethods( ), ); $implemented_interface_storage = $storage_provider->get($implemented_interface); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -365,7 +352,9 @@ private function populateOverriddenMethods( $declaring_method_name = $declaring_method_id->method_name; $declaring_class_storage = $declaring_class_storages[$declaring_class]; - $declaring_method_storage = $declaring_class_storage->methods[$declaring_method_name]; + $declaring_method_storage = $declaring_class_storage->methods[$declaring_method_name] + ?? $declaring_class_storage->pseudo_methods[$declaring_method_name] + ?? $declaring_class_storage->pseudo_static_methods[$declaring_method_name]; if (($declaring_method_storage->has_docblock_param_types || $declaring_method_storage->has_docblock_return_type) @@ -426,7 +415,7 @@ private function populateDataFromTrait( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes, - string $used_trait_lc + string $used_trait_lc, ): void { try { $used_trait_lc = strtolower( @@ -435,7 +424,7 @@ private function populateDataFromTrait( ), ); $trait_storage = $storage_provider->get($used_trait_lc); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return; } @@ -458,7 +447,7 @@ private function populateDataFromTrait( private static function extendType( Union $type, - ClassLikeStorage $storage + ClassLikeStorage $storage, ): Union { $extended_types = []; @@ -491,7 +480,7 @@ private function populateDataFromParentClass( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes, - string $parent_storage_class + string $parent_storage_class, ): void { $parent_storage_class = strtolower( $this->classlikes->getUnAliasedName( @@ -501,7 +490,7 @@ private function populateDataFromParentClass( try { $parent_storage = $storage_provider->get($parent_storage_class); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n"); $storage->invalid_dependencies[$parent_storage_class] = true; @@ -513,32 +502,26 @@ private function populateDataFromParentClass( $this->populateClassLikeStorage($parent_storage, $dependent_classlikes); - $storage->parent_classes = array_merge($storage->parent_classes, $parent_storage->parent_classes); + $storage->parent_classes = [...$storage->parent_classes, ...$parent_storage->parent_classes]; self::extendTemplateParams($storage, $parent_storage, true); $this->inheritMethodsFromParent($storage, $parent_storage); $this->inheritPropertiesFromParent($storage, $parent_storage); - $storage->class_implements = array_merge($storage->class_implements, $parent_storage->class_implements); - $storage->invalid_dependencies = array_merge( - $storage->invalid_dependencies, - $parent_storage->invalid_dependencies, - ); + $storage->class_implements = [...$storage->class_implements, ...$parent_storage->class_implements]; + $storage->invalid_dependencies = [...$storage->invalid_dependencies, ...$parent_storage->invalid_dependencies]; if ($parent_storage->has_visitor_issues) { $storage->has_visitor_issues = true; } - $storage->constants = array_merge( - array_filter( - $parent_storage->constants, - static fn(ClassConstantStorage $constant): bool - => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC - || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED, - ), - $storage->constants, - ); + $storage->constants = [...array_filter( + $parent_storage->constants, + static fn(ClassConstantStorage $constant): bool + => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC + || $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED, + ), ...$storage->constants]; if ($parent_storage->preserve_constructor_signature) { $storage->preserve_constructor_signature = true; @@ -562,34 +545,42 @@ private function populateDataFromParentClass( $parent_storage->dependent_classlikes[strtolower($storage->name)] = true; - $storage->pseudo_static_methods += $parent_storage->pseudo_static_methods; - - $storage->pseudo_methods += $parent_storage->pseudo_methods; - $storage->declaring_pseudo_method_ids += $parent_storage->declaring_pseudo_method_ids; + foreach ($parent_storage->pseudo_static_methods as $method_name => $pseudo_method) { + if (!isset($storage->methods[$method_name])) { + $storage->pseudo_static_methods[$method_name] = $pseudo_method; + } + } + foreach ($parent_storage->pseudo_methods as $method_name => $pseudo_method) { + if (!isset($storage->methods[$method_name])) { + $storage->pseudo_methods[$method_name] = $pseudo_method; + } + } + foreach ($parent_storage->declaring_pseudo_method_ids as $method_name => $pseudo_method_id) { + if (!isset($storage->methods[$method_name])) { + $storage->declaring_pseudo_method_ids[$method_name] = $pseudo_method_id; + }; + } } private function populateInterfaceData( ClassLikeStorage $storage, ClassLikeStorage $interface_storage, ClassLikeStorageProvider $storage_provider, - array $dependent_classlikes + array $dependent_classlikes, ): void { $this->populateClassLikeStorage($interface_storage, $dependent_classlikes); // copy over any constants - $storage->constants = array_merge( - array_filter( - $interface_storage->constants, - static fn(ClassConstantStorage $constant): bool - => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC, - ), - $storage->constants, - ); + $storage->constants = [...array_filter( + $interface_storage->constants, + static fn(ClassConstantStorage $constant): bool + => $constant->visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC, + ), ...$storage->constants]; - $storage->invalid_dependencies = array_merge( - $storage->invalid_dependencies, - $interface_storage->invalid_dependencies, - ); + $storage->invalid_dependencies = [ + ...$storage->invalid_dependencies, + ...$interface_storage->invalid_dependencies, + ]; self::extendTemplateParams($storage, $interface_storage, false); @@ -603,7 +594,7 @@ private function populateInterfaceData( ), ); $new_parent_interface_storage = $storage_provider->get($new_parent); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -614,7 +605,7 @@ private function populateInterfaceData( private static function extendTemplateParams( ClassLikeStorage $storage, ClassLikeStorage $parent_storage, - bool $from_direct_parent + bool $from_direct_parent, ): void { if ($parent_storage->yield && !$storage->yield) { $storage->yield = $parent_storage->yield; @@ -674,7 +665,7 @@ private function populateInterfaceDataFromParentInterface( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes, - string $parent_interface_lc + string $parent_interface_lc, ): void { try { $parent_interface_lc = strtolower( @@ -683,7 +674,7 @@ private function populateInterfaceDataFromParentInterface( ), ); $parent_interface_storage = $storage_provider->get($parent_interface_lc); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n"); $storage->invalid_dependencies[$parent_interface_lc] = true; @@ -697,10 +688,7 @@ private function populateInterfaceDataFromParentInterface( $storage->pseudo_methods += $parent_interface_storage->pseudo_methods; $storage->declaring_pseudo_method_ids += $parent_interface_storage->declaring_pseudo_method_ids; - $storage->parent_interfaces = array_merge( - $parent_interface_storage->parent_interfaces, - $storage->parent_interfaces, - ); + $storage->parent_interfaces = [...$parent_interface_storage->parent_interfaces, ...$storage->parent_interfaces]; if (isset($storage->parent_interfaces[strtolower(UnitEnum::class)])) { $storage->declaring_property_ids['name'] = $storage->name; @@ -720,7 +708,7 @@ private function populateDataFromImplementedInterface( ClassLikeStorage $storage, ClassLikeStorageProvider $storage_provider, array $dependent_classlikes, - string $implemented_interface_lc + string $implemented_interface_lc, ): void { try { $implemented_interface_lc = strtolower( @@ -729,7 +717,7 @@ private function populateDataFromImplementedInterface( ), ); $implemented_interface_storage = $storage_provider->get($implemented_interface_lc); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $this->progress->debug('Populator could not find dependency (' . __LINE__ . ")\n"); $storage->invalid_dependencies[$implemented_interface_lc] = true; @@ -743,10 +731,10 @@ private function populateDataFromImplementedInterface( $dependent_classlikes, ); - $storage->class_implements = array_merge( - $storage->class_implements, - $implemented_interface_storage->parent_interfaces, - ); + $storage->class_implements = [ + ...$storage->class_implements, + ...$implemented_interface_storage->parent_interfaces, + ]; } /** @@ -771,7 +759,7 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file foreach ($storage->required_file_paths as $included_file_path => $_) { try { $included_file_storage = $this->file_storage_provider->get($included_file_path); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -783,25 +771,25 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file foreach ($all_required_file_paths as $included_file_path => $_) { try { $included_file_storage = $this->file_storage_provider->get($included_file_path); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } - $storage->declaring_function_ids = array_merge( - $included_file_storage->declaring_function_ids, - $storage->declaring_function_ids, - ); + $storage->declaring_function_ids = [ + ...$included_file_storage->declaring_function_ids, + ...$storage->declaring_function_ids, + ]; - $storage->declaring_constants = array_merge( - $included_file_storage->declaring_constants, - $storage->declaring_constants, - ); + $storage->declaring_constants = [ + ...$included_file_storage->declaring_constants, + ...$storage->declaring_constants, + ]; } foreach ($storage->referenced_classlikes as $fq_class_name) { try { $classlike_storage = $this->classlike_storage_provider->get($fq_class_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -811,14 +799,14 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file try { $included_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } foreach ($classlike_storage->used_traits as $used_trait) { try { $trait_storage = $this->classlike_storage_provider->get($used_trait); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -830,20 +818,20 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file $included_trait_file_storage = $this->file_storage_provider->get( $trait_storage->location->file_path, ); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } - $storage->declaring_function_ids = array_merge( - $included_trait_file_storage->declaring_function_ids, - $storage->declaring_function_ids, - ); + $storage->declaring_function_ids = [ + ...$included_trait_file_storage->declaring_function_ids, + ...$storage->declaring_function_ids, + ]; } - $storage->declaring_function_ids = array_merge( - $included_file_storage->declaring_function_ids, - $storage->declaring_function_ids, - ); + $storage->declaring_function_ids = [ + ...$included_file_storage->declaring_function_ids, + ...$storage->declaring_function_ids, + ]; } $storage->required_file_paths = $all_required_file_paths; @@ -851,7 +839,7 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file foreach ($all_required_file_paths as $required_file_path) { try { $required_file_storage = $this->file_storage_provider->get($required_file_path); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -861,7 +849,7 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file foreach ($storage->required_classes as $required_classlike) { try { $classlike_storage = $this->classlike_storage_provider->get($required_classlike); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -871,7 +859,7 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file try { $required_file_storage = $this->file_storage_provider->get($classlike_storage->location->file_path); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { continue; } @@ -883,7 +871,7 @@ private function populateFileStorage(FileStorage $storage, array $dependent_file private function inheritConstantsFromTrait( ClassLikeStorage $storage, - ClassLikeStorage $trait_storage + ClassLikeStorage $trait_storage, ): void { if (!$trait_storage->is_trait) { throw new Exception('Class like storage is not for a trait.'); @@ -917,9 +905,9 @@ private function inheritConstantsFromTrait( } } - protected function inheritMethodsFromParent( + private function inheritMethodsFromParent( ClassLikeStorage $storage, - ClassLikeStorage $parent_storage + ClassLikeStorage $parent_storage, ): void { $fq_class_name = $storage->name; $fq_class_name_lc = strtolower($fq_class_name); @@ -1019,7 +1007,11 @@ protected function inheritMethodsFromParent( $implementing_method_id->fq_class_name, ); - if (!$implementing_class_storage->methods[$implementing_method_id->method_name]->abstract + $method = $implementing_class_storage->methods[$implementing_method_id->method_name] + ?? $implementing_class_storage->pseudo_methods[$implementing_method_id->method_name] + ?? $implementing_class_storage->pseudo_static_methods[$implementing_method_id->method_name]; + + if (!$method->abstract || !empty($storage->methods[$implementing_method_id->method_name]->abstract) ) { continue; @@ -1034,7 +1026,7 @@ protected function inheritMethodsFromParent( private function inheritPropertiesFromParent( ClassLikeStorage $storage, - ClassLikeStorage $parent_storage + ClassLikeStorage $parent_storage, ): void { if ($parent_storage->sealed_properties !== null) { $storage->sealed_properties = $parent_storage->sealed_properties; diff --git a/src/Psalm/Internal/Codebase/Properties.php b/src/Psalm/Internal/Codebase/Properties.php index 9dad3906fc2..e8ed2cf2dc4 100644 --- a/src/Psalm/Internal/Codebase/Properties.php +++ b/src/Psalm/Internal/Codebase/Properties.php @@ -1,5 +1,7 @@ classlike_storage_provider = $storage_provider; - $this->file_reference_provider = $file_reference_provider; $this->property_existence_provider = new PropertyExistenceProvider(); $this->property_visibility_provider = new PropertyVisibilityProvider(); $this->property_type_provider = new PropertyTypeProvider(); - $this->classlikes = $classlikes; } /** @@ -61,7 +54,7 @@ public function propertyExists( bool $read_mode, ?StatementsSource $source = null, ?Context $context = null, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): bool { // remove leading backslash if it exists $property_id = ltrim($property_id, '\\'); @@ -168,7 +161,7 @@ public function propertyExists( public function getDeclaringClassForProperty( string $property_id, bool $read_mode, - ?StatementsSource $source = null + ?StatementsSource $source = null, ): ?string { [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -199,7 +192,7 @@ public function getDeclaringClassForProperty( public function getAppearingClassForProperty( string $property_id, bool $read_mode, - ?StatementsSource $source = null + ?StatementsSource $source = null, ): ?string { [$fq_class_name, $property_name] = explode('::$', $property_id); @@ -269,7 +262,7 @@ public function getPropertyType( string $property_id, bool $property_set, ?StatementsSource $source = null, - ?Context $context = null + ?Context $context = null, ): ?Union { // remove leading backslash if it exists $property_id = ltrim($property_id, '\\'); diff --git a/src/Psalm/Internal/Codebase/PropertyMap.php b/src/Psalm/Internal/Codebase/PropertyMap.php index 36e9da02d6b..384bdea4705 100644 --- a/src/Psalm/Internal/Codebase/PropertyMap.php +++ b/src/Psalm/Internal/Codebase/PropertyMap.php @@ -1,5 +1,7 @@ */ private static array $builtin_functions = []; - public function __construct(ClassLikeStorageProvider $storage_provider, Codebase $codebase) - { - $this->storage_provider = $storage_provider; - $this->codebase = $codebase; + public function __construct( + private readonly ClassLikeStorageProvider $storage_provider, + private readonly Codebase $codebase, + ) { self::$builtin_functions = []; } @@ -69,7 +66,7 @@ public function registerClass(ReflectionClass $reflected_class): void $this->storage_provider->get($class_name_lower); return; - } catch (Exception $e) { + } catch (Exception) { // this is fine } @@ -409,7 +406,7 @@ public function registerFunction(string $function_id): ?bool } $storage->cased_name = $reflection_function->getName(); - } catch (ReflectionException $e) { + } catch (ReflectionException) { return false; } @@ -426,7 +423,6 @@ public static function getPsalmTypeFromReflectionType(?ReflectionType $reflectio if ($reflection_type instanceof ReflectionNamedType) { $type = $reflection_type->getName(); } elseif ($reflection_type instanceof ReflectionUnionType) { - /** @psalm-suppress MixedArgument */ $type = implode( '|', array_map( @@ -435,7 +431,7 @@ public static function getPsalmTypeFromReflectionType(?ReflectionType $reflectio ), ); } else { - throw new LogicException('Unexpected reflection class ' . get_class($reflection_type) . ' found.'); + throw new LogicException('Unexpected reflection class ' . $reflection_type::class . ' found.'); } if ($reflection_type->allowsNull()) { @@ -447,7 +443,7 @@ public static function getPsalmTypeFromReflectionType(?ReflectionType $reflectio private function registerInheritedMethods( string $fq_class_name, - string $parent_class + string $parent_class, ): void { $parent_storage = $this->storage_provider->get($parent_class); $storage = $this->storage_provider->get($fq_class_name); @@ -473,7 +469,7 @@ private function registerInheritedMethods( */ private function registerInheritedProperties( string $fq_class_name, - string $parent_class + string $parent_class, ): void { $parent_storage = $this->storage_provider->get($parent_class); $storage = $this->storage_provider->get($fq_class_name); diff --git a/src/Psalm/Internal/Codebase/Scanner.php b/src/Psalm/Internal/Codebase/Scanner.php index 40baaa33d14..85798191789 100644 --- a/src/Psalm/Internal/Codebase/Scanner.php +++ b/src/Psalm/Internal/Codebase/Scanner.php @@ -1,8 +1,9 @@ */ @@ -132,36 +132,17 @@ final class Scanner */ private array $reflected_classlikes_lc = []; - private Reflection $reflection; - - private Config $config; - - private Progress $progress; - - private FileStorageProvider $file_storage_provider; - - private FileProvider $file_provider; - - private FileReferenceProvider $file_reference_provider; - private bool $is_forked = false; public function __construct( - Codebase $codebase, - Config $config, - FileStorageProvider $file_storage_provider, - FileProvider $file_provider, - Reflection $reflection, - FileReferenceProvider $file_reference_provider, - Progress $progress + private readonly Codebase $codebase, + private readonly Config $config, + private readonly FileStorageProvider $file_storage_provider, + private readonly FileProvider $file_provider, + private readonly Reflection $reflection, + private readonly FileReferenceProvider $file_reference_provider, + private readonly Progress $progress, ) { - $this->codebase = $codebase; - $this->reflection = $reflection; - $this->file_provider = $file_provider; - $this->progress = $progress; - $this->file_storage_provider = $file_storage_provider; - $this->config = $config; - $this->file_reference_provider = $file_reference_provider; } /** @@ -226,7 +207,7 @@ public function queueClassLikeForScanning( string $fq_classlike_name, bool $analyze_too = false, bool $store_failure = true, - array $phantom_classes = [] + array $phantom_classes = [], ): void { if ($fq_classlike_name[0] === '\\') { $fq_classlike_name = substr($fq_classlike_name, 1); @@ -239,7 +220,7 @@ public function queueClassLikeForScanning( } // avoid checking classes that we know will just end in failure - if ($fq_classlike_name_lc === 'null' || substr($fq_classlike_name_lc, -5) === '\null') { + if ($fq_classlike_name_lc === 'null' || str_ends_with($fq_classlike_name_lc, '\null')) { return; } @@ -300,7 +281,7 @@ private function scanFilePaths(int $pool_size): bool { $files_to_scan = array_filter( $this->files_to_scan, - [$this, 'shouldScan'], + $this->shouldScan(...), ); $this->files_to_scan = []; @@ -315,6 +296,7 @@ private function scanFilePaths(int $pool_size): bool $pool_size = 1; } + $this->progress->expand(count($files_to_scan)); if ($pool_size > 1) { $process_file_paths = []; @@ -347,13 +329,12 @@ function (): void { $this->progress->debug('Have initialised forked process for scanning' . PHP_EOL); }, - Closure::fromCallable([$this, 'scanAPath']), + $this->scanAPath(...), /** * @return PoolData */ function () { $this->progress->debug('Collecting data from forked scanner process' . PHP_EOL); - $project_analyzer = ProjectAnalyzer::getInstance(); $codebase = $project_analyzer->getCodebase(); $statements_provider = $codebase->statements_provider; @@ -375,6 +356,9 @@ function () { 'taint_data' => $codebase->taint_flow_graph, ]; }, + function (): void { + $this->progress->taskDone(0); + }, ); // Wait for all tasks to complete and collect the results. @@ -421,6 +405,7 @@ function () { $i = 0; foreach ($files_to_scan as $file_path => $_) { + $this->progress->taskDone(0); $this->scanAPath($i, $file_path); ++$i; } @@ -520,7 +505,7 @@ private function convertClassesToFilePaths(ClassLikes $classlikes): void private function scanFile( string $file_path, array $filetype_scanners, - bool $will_analyze = false + bool $will_analyze = false, ): void { $file_scanner = $this->getScannerForPath($file_path, $filetype_scanners, $will_analyze); @@ -614,7 +599,7 @@ private function scanFile( private function getScannerForPath( string $file_path, array $filetype_scanners, - bool $will_analyze = false + bool $will_analyze = false, ): FileScanner { $path_parts = explode(DIRECTORY_SEPARATOR, $file_path); $file_name_parts = explode('.', array_pop($path_parts)); @@ -660,7 +645,7 @@ private function fileExistsForClassLike(ClassLikes $classlikes, string $fq_class $classlikes->addFullyQualifiedClassLikeName( $fq_class_name_lc, - realpath($composer_file_path), + (string) realpath($composer_file_path), ); return true; @@ -676,7 +661,7 @@ function () use ($fq_class_name): ?ReflectionClass { /** @psalm-suppress ArgumentTypeCoercion */ return new ReflectionClass($fq_class_name); - } catch (Throwable $e) { + } catch (Throwable) { // do not cache any results here (as case-sensitive filenames can screw things up) return null; diff --git a/src/Psalm/Internal/Codebase/StorageByPatternResolver.php b/src/Psalm/Internal/Codebase/StorageByPatternResolver.php index ed5baed6dba..67a70a72003 100644 --- a/src/Psalm/Internal/Codebase/StorageByPatternResolver.php +++ b/src/Psalm/Internal/Codebase/StorageByPatternResolver.php @@ -10,8 +10,8 @@ use function preg_match; use function sprintf; +use function str_contains; use function str_replace; -use function strpos; /** * @internal @@ -26,11 +26,11 @@ final class StorageByPatternResolver */ public function resolveConstants( ClassLikeStorage $class_like_storage, - string $pattern + string $pattern, ): array { $constants = $class_like_storage->constants; - if (strpos($pattern, '*') === false) { + if (!str_contains($pattern, '*')) { if (isset($constants[$pattern])) { return [$pattern => $constants[$pattern]]; } @@ -59,10 +59,10 @@ public function resolveConstants( */ public function resolveEnums( ClassLikeStorage $class_like_storage, - string $pattern + string $pattern, ): array { $enum_cases = $class_like_storage->enum_cases; - if (strpos($pattern, '*') === false) { + if (!str_contains($pattern, '*')) { if (isset($enum_cases[$pattern])) { return [$pattern => $enum_cases[$pattern]]; } diff --git a/src/Psalm/Internal/Codebase/TaintFlowGraph.php b/src/Psalm/Internal/Codebase/TaintFlowGraph.php index bb7cd993879..88b02d3169e 100644 --- a/src/Psalm/Internal/Codebase/TaintFlowGraph.php +++ b/src/Psalm/Internal/Codebase/TaintFlowGraph.php @@ -1,5 +1,7 @@ getSpecializedSources($source); foreach ($generated_sources as $generated_source) { - $new_sources = array_merge( - $new_sources, - $this->getChildNodes( - $generated_source, - $source_taints, - $sinks, - $visited_source_ids, - ), - ); + $new_sources = [...$new_sources, ...$this->getChildNodes( + $generated_source, + $source_taints, + $sinks, + $visited_source_ids, + )]; } } @@ -245,7 +247,7 @@ private function getChildNodes( DataFlowNode $generated_source, array $source_taints, array $sinks, - array $visited_source_ids + array $visited_source_ids, ): array { $new_sources = []; @@ -313,150 +315,122 @@ private function getChildNodes( . ' -> ' . $this->getSuccessorPath($sinks[$to_id]); foreach ($matching_taints as $matching_taint) { - switch ($matching_taint) { - case TaintKind::INPUT_CALLABLE: - $issue = new TaintedCallable( - 'Detected tainted text', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_UNSERIALIZE: - $issue = new TaintedUnserialize( - 'Detected tainted code passed to unserialize or similar', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_INCLUDE: - $issue = new TaintedInclude( - 'Detected tainted code passed to include or similar', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_EVAL: - $issue = new TaintedEval( - 'Detected tainted code passed to eval or similar', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_SQL: - $issue = new TaintedSql( - 'Detected tainted SQL', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_HTML: - $issue = new TaintedHtml( - 'Detected tainted HTML', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_HAS_QUOTES: - $issue = new TaintedTextWithQuotes( - 'Detected tainted text with possible quotes', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_SHELL: - $issue = new TaintedShell( - 'Detected tainted shell code', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::USER_SECRET: - $issue = new TaintedUserSecret( - 'Detected tainted user secret leaking', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::SYSTEM_SECRET: - $issue = new TaintedSystemSecret( - 'Detected tainted system secret leaking', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_SSRF: - $issue = new TaintedSSRF( - 'Detected tainted network request', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_LDAP: - $issue = new TaintedLdap( - 'Detected tainted LDAP request', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_COOKIE: - $issue = new TaintedCookie( - 'Detected tainted cookie', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_FILE: - $issue = new TaintedFile( - 'Detected tainted file handling', - $issue_location, - $issue_trace, - $path, - ); - break; - - case TaintKind::INPUT_HEADER: - $issue = new TaintedHeader( - 'Detected tainted header', - $issue_location, - $issue_trace, - $path, - ); - break; - - default: - $issue = new TaintedCustom( - 'Detected tainted ' . $matching_taint, - $issue_location, - $issue_trace, - $path, - ); - } + $issue = match ($matching_taint) { + TaintKind::INPUT_CALLABLE => new TaintedCallable( + 'Detected tainted text', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_UNSERIALIZE => new TaintedUnserialize( + 'Detected tainted code passed to unserialize or similar', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_INCLUDE => new TaintedInclude( + 'Detected tainted code passed to include or similar', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_EVAL => new TaintedEval( + 'Detected tainted code passed to eval or similar', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_SQL => new TaintedSql( + 'Detected tainted SQL', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_HTML => new TaintedHtml( + 'Detected tainted HTML', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_HAS_QUOTES => new TaintedTextWithQuotes( + 'Detected tainted text with possible quotes', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_SHELL => new TaintedShell( + 'Detected tainted shell code', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::USER_SECRET => new TaintedUserSecret( + 'Detected tainted user secret leaking', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::SYSTEM_SECRET => new TaintedSystemSecret( + 'Detected tainted system secret leaking', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_SSRF => new TaintedSSRF( + 'Detected tainted network request', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_LDAP => new TaintedLdap( + 'Detected tainted LDAP request', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_COOKIE => new TaintedCookie( + 'Detected tainted cookie', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_FILE => new TaintedFile( + 'Detected tainted file handling', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_HEADER => new TaintedHeader( + 'Detected tainted header', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_XPATH => new TaintedXpath( + 'Detected tainted xpath query', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_SLEEP => new TaintedSleep( + 'Detected tainted sleep', + $issue_location, + $issue_trace, + $path, + ), + TaintKind::INPUT_EXTRACT => new TaintedExtract( + 'Detected tainted extract', + $issue_location, + $issue_trace, + $path, + ), + default => new TaintedCustom( + 'Detected tainted ' . $matching_taint, + $issue_location, + $issue_trace, + $path, + ), + }; IssueBuffer::maybeAdd($issue); } @@ -521,7 +495,7 @@ private function getSpecializedSources(DataFlowNode $source): array return array_filter( $generated_sources, - [$this, 'doesForwardEdgeExist'], + $this->doesForwardEdgeExist(...), ); } diff --git a/src/Psalm/Internal/Codebase/VariableUseGraph.php b/src/Psalm/Internal/Codebase/VariableUseGraph.php index d90b2878c00..615f7482ee9 100644 --- a/src/Psalm/Internal/Codebase/VariableUseGraph.php +++ b/src/Psalm/Internal/Codebase/VariableUseGraph.php @@ -1,5 +1,7 @@ > */ - protected array $backward_edges = []; + private array $backward_edges = []; /** @var array */ private array $nodes = []; @@ -38,7 +39,7 @@ public function addPath( DataFlowNode $to, string $path_type, ?array $added_taints = null, - ?array $removed_taints = null + ?array $removed_taints = null, ): void { $from_id = $from->id; $to_id = $to->id; @@ -83,10 +84,7 @@ public function isVariableUsed(DataFlowNode $assignment_node): bool return true; } - $new_child_nodes = array_merge( - $new_child_nodes, - $child_nodes, - ); + $new_child_nodes = [...$new_child_nodes, ...$child_nodes]; } $sources = $new_child_nodes; @@ -146,7 +144,7 @@ public function getOriginLocations(DataFlowNode $assignment_node): array */ private function getChildNodes( DataFlowNode $generated_source, - array $visited_source_ids + array $visited_source_ids, ): ?array { $new_child_nodes = []; @@ -202,7 +200,7 @@ private function getChildNodes( */ private function getParentNodes( DataFlowNode $destination, - array $visited_source_ids + array $visited_source_ids, ): array { $new_parent_nodes = []; diff --git a/src/Psalm/Internal/Composer.php b/src/Psalm/Internal/Composer.php index 52ac9350ac3..5810e643907 100644 --- a/src/Psalm/Internal/Composer.php +++ b/src/Psalm/Internal/Composer.php @@ -1,5 +1,7 @@ */ - public array $taints; - /** @var ?self */ public ?DataFlowNode $previous = null; @@ -40,23 +34,17 @@ class DataFlowNode * @param array $taints */ public function __construct( - string $id, - string $label, - ?CodeLocation $code_location, + public string $id, + public string $label, + public ?CodeLocation $code_location, ?string $specialization_key = null, - array $taints = [] + public array $taints = [], ) { - $this->id = $id; - if ($specialization_key) { $this->unspecialized_id = $id; $this->id .= '-' . $specialization_key; } - - $this->label = $label; - $this->code_location = $code_location; $this->specialization_key = $specialization_key; - $this->taints = $taints; } /** @@ -67,7 +55,7 @@ final public static function getForMethodArgument( string $cased_method_id, int $argument_offset, ?CodeLocation $arg_location, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): self { $arg_id = strtolower($method_id) . '#' . ($argument_offset + 1); @@ -93,7 +81,7 @@ final public static function getForMethodArgument( final public static function getForAssignment( string $var_id, CodeLocation $assignment_location, - ?string $specialization_key = null + ?string $specialization_key = null, ): self { $id = $var_id . '-' . $assignment_location->file_name @@ -110,7 +98,7 @@ final public static function getForMethodReturn( string $method_id, string $cased_method_id, ?CodeLocation $code_location, - ?CodeLocation $function_location = null + ?CodeLocation $function_location = null, ): self { $specialization_key = null; diff --git a/src/Psalm/Internal/DataFlow/Path.php b/src/Psalm/Internal/DataFlow/Path.php index c6c0e279761..785bb8e511a 100644 --- a/src/Psalm/Internal/DataFlow/Path.php +++ b/src/Psalm/Internal/DataFlow/Path.php @@ -1,5 +1,7 @@ */ - public ?array $unescaped_taints = null; - - /** @var ?array */ - public ?array $escaped_taints = null; - - public int $length; - /** * @param ?array $unescaped_taints * @param ?array $escaped_taints */ public function __construct( - string $type, - int $length, - ?array $unescaped_taints = null, - ?array $escaped_taints = null + public readonly string $type, + public readonly int $length, + public readonly ?array $unescaped_taints = null, + public readonly ?array $escaped_taints = null, ) { - $this->type = $type; - $this->length = $length; - $this->unescaped_taints = $unescaped_taints; - $this->escaped_taints = $escaped_taints; } } diff --git a/src/Psalm/Internal/DataFlow/TaintSink.php b/src/Psalm/Internal/DataFlow/TaintSink.php index 997b63b97c8..42a75b2f4c8 100644 --- a/src/Psalm/Internal/DataFlow/TaintSink.php +++ b/src/Psalm/Internal/DataFlow/TaintSink.php @@ -1,5 +1,7 @@ type = $type; - $this->old = $old; - $this->new = $new; + public function __construct( + /** @var int One of the TYPE_* constants */ + public readonly int $type, + /** @var mixed Is null for add operations */ + public readonly mixed $old, + /** @var mixed Is null for remove operations */ + public readonly mixed $new, + ) { } } diff --git a/src/Psalm/Internal/Diff/FileDiffer.php b/src/Psalm/Internal/Diff/FileDiffer.php index 801dae66173..e6812050b3d 100644 --- a/src/Psalm/Internal/Diff/FileDiffer.php +++ b/src/Psalm/Internal/Diff/FileDiffer.php @@ -33,7 +33,7 @@ final class FileDiffer */ private static function calculateTrace( array $a, - array $b + array $b, ): array { $n = count($a); $m = count($b); diff --git a/src/Psalm/Internal/Diff/FileStatementsDiffer.php b/src/Psalm/Internal/Diff/FileStatementsDiffer.php index f8197fc12c3..b00f044207e 100644 --- a/src/Psalm/Internal/Diff/FileStatementsDiffer.php +++ b/src/Psalm/Internal/Diff/FileStatementsDiffer.php @@ -1,11 +1,13 @@ alias; } else { $name_parts = $use->name->getParts(); + assert(!empty($name_parts)); $add_or_delete[] = 'use:' . end($name_parts); } @@ -161,6 +164,7 @@ static function ( $add_or_delete[] = 'use:' . (string) $use->alias; } else { $name_parts = $use->name->getParts(); + assert(!empty($name_parts)); $add_or_delete[] = 'use:' . end($name_parts); } diff --git a/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php b/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php index b8ea37c5e16..d16926d526a 100644 --- a/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php +++ b/src/Psalm/Internal/Diff/NamespaceStatementsDiffer.php @@ -1,11 +1,13 @@ alias; } else { $name_parts = $use->name->getParts(); + assert(!empty($name_parts)); $add_or_delete[] = 'use:' . end($name_parts); } @@ -129,6 +132,7 @@ static function ( $add_or_delete[] = 'use:' . (string) $use->alias; } else { $name_parts = $use->name->getParts(); + assert(!empty($name_parts)); $add_or_delete[] = 'use:' . end($name_parts); } diff --git a/src/Psalm/Internal/ErrorHandler.php b/src/Psalm/Internal/ErrorHandler.php index 9e5eef5917e..fa93aa1a48e 100644 --- a/src/Psalm/Internal/ErrorHandler.php +++ b/src/Psalm/Internal/ErrorHandler.php @@ -1,5 +1,7 @@ $argv */ - public static function install(array $argv = array()): void + public static function install(array $argv = []): void { self::$args = implode(' ', $argv); self::setErrorReporting(); @@ -58,7 +60,7 @@ private function __construct() private static function setErrorReporting(): void { - error_reporting(E_ALL | 2048 /* E_STRICT deprecated in PHP 8.4 */); + error_reporting(E_ALL); ini_set('display_errors', '1'); } @@ -68,7 +70,7 @@ private static function installErrorHandler(): void int $error_code, string $error_message, string $error_filename = 'unknown', - int $error_line = -1 + int $error_line = -1, ): bool { if (ErrorHandler::$exceptions_enabled && ($error_code & error_reporting())) { throw new RuntimeException( @@ -90,7 +92,7 @@ private static function installExceptionHandler(): void * then print more of the backtrace than is done by default to stderr, * then exit with a non-zero exit code to indicate failure. */ - set_exception_handler(static function (Throwable $throwable): void { + set_exception_handler(static function (Throwable $throwable): never { fwrite(STDERR, "Uncaught $throwable\n"); $version = defined('PSALM_VERSION') ? PSALM_VERSION : '(unknown version)'; fwrite(STDERR, "(Psalm $version crashed due to an uncaught Throwable)\n"); diff --git a/src/Psalm/Internal/EventDispatcher.php b/src/Psalm/Internal/EventDispatcher.php index 989e8ec1f3b..e09a2b03b02 100644 --- a/src/Psalm/Internal/EventDispatcher.php +++ b/src/Psalm/Internal/EventDispatcher.php @@ -1,5 +1,7 @@ env = $env; + private array $readEnv = []; + + public function __construct( + /** + * Environment variables. + * + * Overwritten through collection process. + */ + protected array $env, + ) { } // API @@ -69,7 +70,7 @@ public function collect(): array * @return $this * @psalm-suppress PossiblyUndefinedStringArrayOffset */ - protected function fillTravisCi(): self + private function fillTravisCi(): self { if (isset($this->env['TRAVIS']) && $this->env['TRAVIS'] && isset($this->env['TRAVIS_JOB_ID'])) { $this->readEnv['CI_JOB_ID'] = $this->env['TRAVIS_JOB_ID']; @@ -112,7 +113,7 @@ protected function fillTravisCi(): self * * @return $this */ - protected function fillCircleCi(): self + private function fillCircleCi(): self { if (isset($this->env['CIRCLECI']) && $this->env['CIRCLECI'] && isset($this->env['CIRCLE_BUILD_NUM'])) { $this->env['CI_BUILD_NUMBER'] = $this->env['CIRCLE_BUILD_NUM']; @@ -145,7 +146,7 @@ protected function fillCircleCi(): self * @psalm-suppress PossiblyUndefinedStringArrayOffset * @return $this */ - protected function fillAppVeyor(): self + private function fillAppVeyor(): self { if (isset($this->env['APPVEYOR']) && $this->env['APPVEYOR'] && isset($this->env['APPVEYOR_BUILD_NUMBER'])) { $this->readEnv['CI_BUILD_NUMBER'] = $this->env['APPVEYOR_BUILD_NUMBER']; @@ -192,7 +193,7 @@ protected function fillAppVeyor(): self * * @return $this */ - protected function fillJenkins(): self + private function fillJenkins(): self { if (isset($this->env['JENKINS_URL']) && isset($this->env['BUILD_NUMBER'])) { $this->readEnv['CI_BUILD_NUMBER'] = $this->env['BUILD_NUMBER']; @@ -216,7 +217,7 @@ protected function fillJenkins(): self * @psalm-suppress PossiblyUndefinedStringArrayOffset * @return $this */ - protected function fillScrutinizer(): self + private function fillScrutinizer(): self { if (isset($this->env['SCRUTINIZER']) && $this->env['SCRUTINIZER']) { $this->readEnv['CI_JOB_ID'] = $this->env['SCRUTINIZER_INSPECTION_UUID']; @@ -250,16 +251,16 @@ protected function fillScrutinizer(): self * @return $this * @psalm-suppress PossiblyUndefinedStringArrayOffset */ - protected function fillGithubActions(): BuildInfoCollector + private function fillGithubActions(): BuildInfoCollector { if (isset($this->env['GITHUB_ACTIONS'])) { $this->env['CI_NAME'] = 'github-actions'; $this->env['CI_JOB_ID'] = $this->env['GITHUB_ACTIONS']; $githubRef = (string) $this->env['GITHUB_REF']; - if (strpos($githubRef, 'refs/heads/') !== false) { + if (str_contains($githubRef, 'refs/heads/')) { $githubRef = str_replace('refs/heads/', '', $githubRef); - } elseif (strpos($githubRef, 'refs/tags/') !== false) { + } elseif (str_contains($githubRef, 'refs/tags/')) { $githubRef = str_replace('refs/tags/', '', $githubRef); } @@ -277,6 +278,7 @@ protected function fillGithubActions(): BuildInfoCollector if (isset($this->env['GITHUB_EVENT_PATH'])) { $event_json = file_get_contents((string) $this->env['GITHUB_EVENT_PATH']); + assert($event_json !== false); /** @var array */ $event_data = json_decode($event_json, true, 512, JSON_THROW_ON_ERROR); @@ -300,7 +302,7 @@ protected function fillGithubActions(): BuildInfoCollector ->setCommitterName($head_commit_data['committer']['name']) ->setCommitterEmail($head_commit_data['committer']['email']) ->setMessage($head_commit_data['message']) - ->setDate(strtotime($head_commit_data['timestamp'])), + ->setDate((int) strtotime($head_commit_data['timestamp'])), [], ); diff --git a/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php b/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php index 2330062120e..f659fef639d 100644 --- a/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php +++ b/src/Psalm/Internal/ExecutionEnvironment/GitInfoCollector.php @@ -1,5 +1,7 @@ executor->execute('git branch'); foreach ($branchesResult as $result) { - if (strpos($result, '* ') === 0) { + if (str_starts_with($result, '* ')) { $exploded = explode('* ', $result, 2); return $exploded[1]; @@ -75,7 +78,7 @@ protected function collectBranch(): string * * @throws RuntimeException */ - protected function collectCommit(): CommitInfo + private function collectCommit(): CommitInfo { $commitResult = $this->executor->execute('git log -1 --pretty=format:%H%n%aN%n%ae%n%cN%n%ce%n%s%n%at'); @@ -101,7 +104,7 @@ protected function collectCommit(): CommitInfo * @throws RuntimeException * @return list */ - protected function collectRemotes(): array + private function collectRemotes(): array { $remotesResult = $this->executor->execute('git remote -v'); @@ -113,7 +116,7 @@ protected function collectRemotes(): array $results = []; foreach ($remotesResult as $result) { - if (strpos($result, ' ') !== false) { + if (str_contains($result, ' ')) { [$remote] = explode(' ', $result, 2); $results[] = $remote; @@ -127,7 +130,7 @@ protected function collectRemotes(): array $remotes = []; foreach ($results as $result) { - if (strpos($result, "\t") !== false) { + if (str_contains($result, "\t")) { [$name, $url] = explode("\t", $result, 2); $remote = new RemoteInfo(); diff --git a/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php b/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php index 20aa0383bd0..8db721f4e34 100644 --- a/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php +++ b/src/Psalm/Internal/ExecutionEnvironment/SystemCommandExecutor.php @@ -1,5 +1,7 @@ getLine()])) { return self::$manipulators[$file_path][$stmt->getLine()]; @@ -52,10 +52,9 @@ public static function getForClass( private function __construct( ProjectAnalyzer $project_analyzer, - Class_ $stmt, - string $file_path + private readonly Class_ $stmt, + string $file_path, ) { - $this->stmt = $stmt; $docblock = $stmt->getDocComment(); $this->docblock_start = $docblock ? $docblock->getStartFilePos() : (int)$stmt->getAttribute('startFilePos'); $this->docblock_end = (int)$stmt->getAttribute('startFilePos'); diff --git a/src/Psalm/Internal/FileManipulation/CodeMigration.php b/src/Psalm/Internal/FileManipulation/CodeMigration.php index e118e195e72..980fa64b088 100644 --- a/src/Psalm/Internal/FileManipulation/CodeMigration.php +++ b/src/Psalm/Internal/FileManipulation/CodeMigration.php @@ -1,5 +1,7 @@ source_file_path = $source_file_path; - $this->source_start = $source_start; - $this->source_end = $source_end; - $this->destination_file_path = $destination_file_path; - $this->destination_start = $destination_start; } } diff --git a/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php b/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php index 7c5870b65c1..6d411f01839 100644 --- a/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php +++ b/src/Psalm/Internal/FileManipulation/FileManipulationBuffer.php @@ -1,5 +1,7 @@ getSnippetBounds(); diff --git a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php index 85ad399f583..3004060d044 100644 --- a/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/FunctionDocblockManipulator.php @@ -1,5 +1,7 @@ getLine()])) { return self::$manipulators[$file_path][$stmt->getLine()]; @@ -109,12 +107,11 @@ public static function getForFunction( return $manipulator; } - /** - * @param Closure|Function_|ClassMethod|ArrowFunction $stmt - */ - private function __construct(string $file_path, FunctionLike $stmt, ProjectAnalyzer $project_analyzer) - { - $this->stmt = $stmt; + private function __construct( + string $file_path, + private readonly Closure|Function_|ClassMethod|ArrowFunction $stmt, + ProjectAnalyzer $project_analyzer, + ) { $docblock = $stmt->getDocComment(); $this->docblock_start = $docblock ? $docblock->getStartFilePos() : (int)$stmt->getAttribute('startFilePos'); $this->docblock_end = $function_start = (int)$stmt->getAttribute('startFilePos'); @@ -273,7 +270,7 @@ public function setReturnType( string $new_type, string $phpdoc_type, bool $is_php_compatible, - ?string $description + ?string $description, ): void { $new_type = str_replace(['', ''], '', $new_type); @@ -291,7 +288,7 @@ public function setParamType( string $param_name, ?string $php_type, string $new_type, - string $phpdoc_type + string $phpdoc_type, ): void { $new_type = str_replace(['', '', ''], '', $new_type); @@ -577,7 +574,7 @@ public static function clearCache(): void */ public static function addManipulators(array $manipulators): void { - self::$manipulators = array_merge($manipulators, self::$manipulators); + self::$manipulators = [...$manipulators, ...self::$manipulators]; } /** diff --git a/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php b/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php index 8e25959cff6..6aa54dbed74 100644 --- a/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php +++ b/src/Psalm/Internal/FileManipulation/PropertyDocblockManipulator.php @@ -1,5 +1,7 @@ getLine()])) { return self::$manipulators[$file_path][$stmt->getLine()]; @@ -73,10 +73,9 @@ public static function getForProperty( private function __construct( ProjectAnalyzer $project_analyzer, - Property $stmt, - string $file_path + private readonly Property $stmt, + string $file_path, ) { - $this->stmt = $stmt; $docblock = $stmt->getDocComment(); $this->docblock_start = $docblock ? $docblock->getStartFilePos() : (int)$stmt->getAttribute('startFilePos'); $this->docblock_end = (int)$stmt->getAttribute('startFilePos'); @@ -139,7 +138,7 @@ public function setType( string $new_type, string $phpdoc_type, bool $is_php_compatible, - ?string $description = null + ?string $description = null, ): void { $new_type = str_replace(['', '', ''], '', $new_type); diff --git a/src/Psalm/Internal/Fork/ForkMessage.php b/src/Psalm/Internal/Fork/ForkMessage.php index bd1e1460a78..5672c7a6554 100644 --- a/src/Psalm/Internal/Fork/ForkMessage.php +++ b/src/Psalm/Internal/Fork/ForkMessage.php @@ -1,5 +1,7 @@ data = $data; } } diff --git a/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php b/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php index 43f4b4ceaa6..e0ba5e0ba16 100644 --- a/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php +++ b/src/Psalm/Internal/Fork/ForkProcessErrorMessage.php @@ -1,5 +1,7 @@ message = $message; } } diff --git a/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php b/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php index 591b472db4b..6cd9ccd09f1 100644 --- a/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php +++ b/src/Psalm/Internal/Fork/ForkTaskDoneMessage.php @@ -1,5 +1,7 @@ data = $data; } } diff --git a/src/Psalm/Internal/Fork/Pool.php b/src/Psalm/Internal/Fork/Pool.php index 9d00a104993..c45cacf9328 100644 --- a/src/Psalm/Internal/Fork/Pool.php +++ b/src/Psalm/Internal/Fork/Pool.php @@ -1,5 +1,7 @@ > $process_task_data_iterator * An array of task data items to be divided up among the @@ -100,16 +96,14 @@ final class Pool * @psalm-suppress MixedAssignment */ public function __construct( - Config $config, + private readonly Config $config, array $process_task_data_iterator, Closure $startup_closure, Closure $task_closure, Closure $shutdown_closure, - ?Closure $task_done_closure = null + private readonly ?Closure $task_done_closure = null, ) { $pool_size = count($process_task_data_iterator); - $this->task_done_closure = $task_done_closure; - $this->config = $config; assert( $pool_size > 1, @@ -123,7 +117,7 @@ public function __construct( exit(1); } - $disabled_functions = array_map('trim', explode(',', ini_get('disable_functions'))); + $disabled_functions = array_map('trim', explode(',', (string) ini_get('disable_functions'))); if (in_array('pcntl_fork', $disabled_functions)) { echo "pcntl_fork() is disabled by php configuration (disable_functions directive).\n" . "Please enable it or run Psalm single-threaded with --threads=1 cli switch.\n"; @@ -190,14 +184,14 @@ public function __construct( $task_done_message = new ForkTaskDoneMessage($task_result); if ($this->config->use_igbinary) { - $encoded_message = base64_encode(igbinary_serialize($task_done_message)); + $encoded_message = base64_encode((string) igbinary_serialize($task_done_message)); } else { $encoded_message = base64_encode(serialize($task_done_message)); } $serialized_message = $task_done_buffer . $encoded_message . "\n"; if (strlen($serialized_message) > 200) { - $bytes_written = @fwrite($write_stream, $serialized_message); + $bytes_written = (int) @fwrite($write_stream, $serialized_message); if (strlen($serialized_message) !== $bytes_written) { $task_done_buffer = substr($serialized_message, $bytes_written); @@ -219,7 +213,7 @@ public function __construct( // This can happen when developing Psalm from source without running `composer update`, // or because of rare bugs in Psalm. $process_done_message = new ForkProcessErrorMessage( - get_class($t) . ' ' . $t->getMessage() . "\n" . + $t::class . ' ' . $t->getMessage() . "\n" . "Emitted in " . $t->getFile() . ":" . $t->getLine() . "\n" . "Stack trace in the forked worker:\n" . $t->getTraceAsString(), @@ -227,7 +221,7 @@ public function __construct( } if ($this->config->use_igbinary) { - $encoded_message = base64_encode(igbinary_serialize($process_done_message)); + $encoded_message = base64_encode((string) igbinary_serialize($process_done_message)); } else { $encoded_message = base64_encode(serialize($process_done_message)); } @@ -238,7 +232,7 @@ public function __construct( while ($bytes_written < $bytes_to_write && !feof($write_stream)) { // attempt to write the remaining unsent part - $bytes_written += @fwrite($write_stream, substr($serialized_message, $bytes_written)); + $bytes_written += (int) @fwrite($write_stream, substr($serialized_message, $bytes_written)); if ($bytes_written < $bytes_to_write) { // wait a bit @@ -360,15 +354,15 @@ private function readResultsFromChildren(): array $content[(int)$file] .= $buffer; } - if (strpos($buffer, "\n") !== false) { + if (str_contains($buffer, "\n")) { $serialized_messages = explode("\n", $content[(int)$file]); $content[(int)$file] = array_pop($serialized_messages); foreach ($serialized_messages as $serialized_message) { if ($this->config->use_igbinary) { - $message = igbinary_unserialize(base64_decode($serialized_message, true)); + $message = igbinary_unserialize((string) base64_decode($serialized_message, true)); } else { - $message = unserialize(base64_decode($serialized_message, true)); + $message = unserialize((string) base64_decode($serialized_message, true)); } if ($message instanceof ForkProcessDoneMessage) { diff --git a/src/Psalm/Internal/Fork/PsalmRestarter.php b/src/Psalm/Internal/Fork/PsalmRestarter.php index 259dd2fe058..67c5b0491e9 100644 --- a/src/Psalm/Internal/Fork/PsalmRestarter.php +++ b/src/Psalm/Internal/Fork/PsalmRestarter.php @@ -1,5 +1,7 @@ true, + 'enable_cli' => 1, 'jit' => 1205, - 'jit_buffer_size' => 512 * 1024 * 1024, + 'validate_timestamps' => 0, + 'file_update_protection' => 0, + 'jit_buffer_size' => 128 * 1024 * 1024, + 'max_accelerated_files' => 1_000_000, + 'interned_strings_buffer' => 64, + 'jit_max_root_traces' => 100_000, + 'jit_max_side_traces' => 100_000, + 'jit_max_exit_counters' => 100_000, + 'jit_hot_loop' => 1, + 'jit_hot_func' => 1, + 'jit_hot_return' => 1, + 'jit_hot_side_exit' => 1, + 'jit_blacklist_root_trace' => 255, + 'jit_blacklist_side_trace' => 255, 'optimization_level' => '0x7FFEBFFF', 'preload' => '', 'log_verbosity_level' => 0, @@ -68,19 +86,18 @@ protected function requiresRestart($default): bool $opcache_loaded = extension_loaded('opcache') || extension_loaded('Zend OPcache'); - if (PHP_VERSION_ID >= 8_00_00 && $opcache_loaded) { + if ($opcache_loaded) { // restart to enable JIT if it's not configured in the optimal way - $opcache_settings = [ - 'enable_cli' => in_array(ini_get('opcache.enable_cli'), ['1', 'true', true, 1]), - 'jit' => (int) ini_get('opcache.jit'), - 'log_verbosity_level' => (int) ini_get('opcache.log_verbosity_level'), - 'optimization_level' => (string) ini_get('opcache.optimization_level'), - 'preload' => (string) ini_get('opcache.preload'), - 'jit_buffer_size' => self::toBytes(ini_get('opcache.jit_buffer_size')), - ]; - foreach (self::REQUIRED_OPCACHE_SETTINGS as $ini_name => $required_value) { - if ($opcache_settings[$ini_name] !== $required_value) { + $value = (string) ini_get("opcache.$ini_name"); + if ($ini_name === 'jit_buffer_size') { + $value = self::toBytes($value); + } elseif ($ini_name === 'enable_cli') { + $value = in_array($value, ['1', 'true', true, 1]) ? 1 : 0; + } elseif (is_int($required_value)) { + $value = (int) $value; + } + if ($value !== $required_value) { return true; } } @@ -133,10 +150,11 @@ private static function toBytes(string $value): int protected function restart($command): void { if ($this->required && $this->tmpIni) { - $regex = '/^\s*(extension\s*=.*(' . implode('|', $this->disabled_extensions) . ').*)$/mi'; + $regex = '/^\s*((?:zend_)?extension\s*=.*(' . implode('|', $this->disabled_extensions) . ').*)$/mi'; $content = file_get_contents($this->tmpIni); + assert($content !== false); - $content = preg_replace($regex, ';$1', $content); + $content = (string) preg_replace($regex, ';$1', $content); file_put_contents($this->tmpIni, $content); } @@ -147,16 +165,13 @@ protected function restart($command): void // executed in the parent process (before restart) // if it wasn't loaded then we apparently don't have opcache installed and there's no point trying // to tweak it - // If we're running on 7.4 there's no JIT available - if (PHP_VERSION_ID >= 8_00_00 && $opcache_loaded) { - $additional_options = [ - '-dopcache.enable_cli=true', - '-dopcache.jit_buffer_size=128M', // JIT on AArch64 doesn't support opcache.jit_buffer_size above 128M - '-dopcache.jit=1205', - '-dopcache.optimization_level=0x7FFEBFFF', - '-dopcache.preload=', - '-dopcache.log_verbosity_level=0', - ]; + if ($opcache_loaded && + !(defined('PHP_WINDOWS_VERSION_MAJOR') && PHP_VERSION_ID < self::MIN_PHP_VERSION_WINDOWS_JIT) + ) { + $additional_options = []; + foreach (self::REQUIRED_OPCACHE_SETTINGS as $key => $value) { + $additional_options []= "-dopcache.{$key}={$value}"; + } } if ($opcache_loaded) { diff --git a/src/Psalm/Internal/IncludeCollector.php b/src/Psalm/Internal/IncludeCollector.php index ba37946d8c9..9b336ecc1a2 100644 --- a/src/Psalm/Internal/IncludeCollector.php +++ b/src/Psalm/Internal/IncludeCollector.php @@ -1,5 +1,7 @@ handler = $handler; + public function __construct( + private readonly ClientHandler $handler, + ) { } public function begin(string $title, ?string $message = null, ?int $percentage = null): void diff --git a/src/Psalm/Internal/LanguageServer/Client/Progress/Progress.php b/src/Psalm/Internal/LanguageServer/Client/Progress/Progress.php index 8a1ca5da3a9..865dcd33e45 100644 --- a/src/Psalm/Internal/LanguageServer/Client/Progress/Progress.php +++ b/src/Psalm/Internal/LanguageServer/Client/Progress/Progress.php @@ -1,5 +1,7 @@ handler = $handler; - $this->token = $token; + public function __construct( + private readonly ClientHandler $handler, + private readonly string $token, + ) { } public function begin( string $title, ?string $message = null, - ?int $percentage = null + ?int $percentage = null, ): void { if ($this->status === self::STATUS_ACTIVE) { throw new LogicException('Progress has already been started'); diff --git a/src/Psalm/Internal/LanguageServer/Client/Progress/ProgressInterface.php b/src/Psalm/Internal/LanguageServer/Client/Progress/ProgressInterface.php index 78f045c2c53..8760e0b4c2e 100644 --- a/src/Psalm/Internal/LanguageServer/Client/Progress/ProgressInterface.php +++ b/src/Psalm/Internal/LanguageServer/Client/Progress/ProgressInterface.php @@ -1,5 +1,7 @@ handler = $handler; - $this->server = $server; + public function __construct( + private readonly ClientHandler $handler, + private readonly LanguageServer $server, + ) { } /** diff --git a/src/Psalm/Internal/LanguageServer/Client/Workspace.php b/src/Psalm/Internal/LanguageServer/Client/Workspace.php index 0df62350373..164a3b83e67 100644 --- a/src/Psalm/Internal/LanguageServer/Client/Workspace.php +++ b/src/Psalm/Internal/LanguageServer/Client/Workspace.php @@ -4,8 +4,6 @@ namespace Psalm\Internal\LanguageServer\Client; -use Amp\Promise; -use JsonMapper; use Psalm\Internal\LanguageServer\ClientHandler; use Psalm\Internal\LanguageServer\LanguageServer; @@ -16,20 +14,10 @@ */ final class Workspace { - private ClientHandler $handler; - - /** - * @psalm-suppress UnusedProperty - */ - private JsonMapper $mapper; - - private LanguageServer $server; - - public function __construct(ClientHandler $handler, JsonMapper $mapper, LanguageServer $server) - { - $this->handler = $handler; - $this->mapper = $mapper; - $this->server = $server; + public function __construct( + private readonly ClientHandler $handler, + private readonly LanguageServer $server, + ) { } /** @@ -42,11 +30,11 @@ public function __construct(ClientHandler $handler, JsonMapper $mapper, Language * @param string $section The configuration section asked for. * @param string|null $scopeUri The scope to get the configuration section for. */ - public function requestConfiguration(string $section, ?string $scopeUri = null): Promise + public function requestConfiguration(string $section, ?string $scopeUri = null): object { $this->server->logDebug("workspace/configuration"); - /** @var Promise */ + /** @var object */ return $this->handler->request('workspace/configuration', [ 'items' => [ [ diff --git a/src/Psalm/Internal/LanguageServer/ClientConfiguration.php b/src/Psalm/Internal/LanguageServer/ClientConfiguration.php index 788d699fc16..00d657c66fc 100644 --- a/src/Psalm/Internal/LanguageServer/ClientConfiguration.php +++ b/src/Psalm/Internal/LanguageServer/ClientConfiguration.php @@ -12,11 +12,6 @@ final class ClientConfiguration { - /** - * Location of Baseline file - */ - public ?string $baseline = null; - /** * TCP Server Address */ @@ -27,68 +22,6 @@ final class ClientConfiguration */ public ?bool $TCPServerMode = null; - /** - * Hide Warnings or not - */ - public ?bool $hideWarnings = null; - - /** - * Provide Completion or not - */ - public ?bool $provideCompletion = null; - - /** - * Provide GoTo Definitions or not - */ - public ?bool $provideDefinition = null; - - /** - * Provide Hover Requests or not - */ - public ?bool $provideHover = null; - - /** - * Provide Signature Help or not - */ - public ?bool $provideSignatureHelp = null; - - /** - * Provide Code Actions or not - */ - public ?bool $provideCodeActions = null; - - /** - * Provide Diagnostics or not - */ - public ?bool $provideDiagnostics = null; - - /** - * Provide Completion or not - * - * @psalm-suppress PossiblyUnusedProperty - */ - public ?bool $findUnusedVariables = null; - - /** - * Look for dead code - * - * @var 'always'|'auto'|null - */ - public ?string $findUnusedCode = null; - - /** - * Log Level - * - * @see MessageType - */ - public ?int $logLevel = null; - - /** - * If added, the language server will not respond to onChange events. - * You can also specify a line count over which Psalm will not run on-change events. - */ - public ?int $onchangeLineLimit = null; - /** * Debounce time in milliseconds for onChange events */ @@ -100,30 +33,59 @@ final class ClientConfiguration * @param 'always'|'auto'|null $findUnusedCode */ public function __construct( - bool $hideWarnings = true, - ?bool $provideCompletion = null, - ?bool $provideDefinition = null, - ?bool $provideHover = null, - ?bool $provideSignatureHelp = null, - ?bool $provideCodeActions = null, - ?bool $provideDiagnostics = null, - ?bool $findUnusedVariables = null, - ?string $findUnusedCode = null, - ?int $logLevel = null, - ?int $onchangeLineLimit = null, - ?string $baseline = null + /** + * Hide Warnings or not + */ + public ?bool $hideWarnings = true, + /** + * Provide Completion or not + */ + public ?bool $provideCompletion = null, + /** + * Provide GoTo Definitions or not + */ + public ?bool $provideDefinition = null, + /** + * Provide Hover Requests or not + */ + public ?bool $provideHover = null, + /** + * Provide Signature Help or not + */ + public ?bool $provideSignatureHelp = null, + /** + * Provide Code Actions or not + */ + public ?bool $provideCodeActions = null, + /** + * Provide Diagnostics or not + */ + public ?bool $provideDiagnostics = null, + /** + * Provide Completion or not + * + * @psalm-suppress PossiblyUnusedProperty + */ + public ?bool $findUnusedVariables = null, + /** + * Look for dead code + */ + public ?string $findUnusedCode = null, + /** + * Log Level + * + * @see MessageType + */ + public ?int $logLevel = null, + /** + * If added, the language server will not respond to onChange events. + * You can also specify a line count over which Psalm will not run on-change events. + */ + public ?int $onchangeLineLimit = null, + /** + * Location of Baseline file + */ + public ?string $baseline = null, ) { - $this->hideWarnings = $hideWarnings; - $this->provideCompletion = $provideCompletion; - $this->provideDefinition = $provideDefinition; - $this->provideHover = $provideHover; - $this->provideSignatureHelp = $provideSignatureHelp; - $this->provideCodeActions = $provideCodeActions; - $this->provideDiagnostics = $provideDiagnostics; - $this->findUnusedVariables = $findUnusedVariables; - $this->findUnusedCode = $findUnusedCode; - $this->logLevel = $logLevel; - $this->onchangeLineLimit = $onchangeLineLimit; - $this->baseline = $baseline; } } diff --git a/src/Psalm/Internal/LanguageServer/ClientHandler.php b/src/Psalm/Internal/LanguageServer/ClientHandler.php index 5c2588516cf..2b6bcc21aee 100644 --- a/src/Psalm/Internal/LanguageServer/ClientHandler.php +++ b/src/Psalm/Internal/LanguageServer/ClientHandler.php @@ -8,27 +8,17 @@ use AdvancedJsonRpc\Request; use AdvancedJsonRpc\Response; use AdvancedJsonRpc\SuccessResponse; -use Amp\Deferred; -use Amp\Promise; -use Generator; - -use function Amp\call; +use Amp\DeferredFuture; /** * @internal */ final class ClientHandler { - public ProtocolReader $protocolReader; - - public ProtocolWriter $protocolWriter; - public IdGenerator $idGenerator; - public function __construct(ProtocolReader $protocolReader, ProtocolWriter $protocolWriter) + public function __construct(public ProtocolReader $protocolReader, public ProtocolWriter $protocolWriter) { - $this->protocolReader = $protocolReader; - $this->protocolWriter = $protocolWriter; $this->idGenerator = new IdGenerator; } @@ -37,24 +27,19 @@ public function __construct(ProtocolReader $protocolReader, ProtocolWriter $prot * * @param string $method The method to call * @param array|object $params The method parameters - * @return Promise Resolved with the result of the request or rejected with an error + * @return mixed Resolved with the result of the request or rejected with an error */ - public function request(string $method, $params): Promise + public function request(string $method, array|object $params): mixed { $id = $this->idGenerator->generate(); - return call( - /** - * @return Generator> - */ - function () use ($id, $method, $params): Generator { - yield $this->protocolWriter->write( + $this->protocolWriter->write( new Message( new Request($id, $method, (object) $params), ), ); - $deferred = new Deferred(); + $deferred = new DeferredFuture(); $listener = function (Message $msg) use ($id, $deferred, &$listener): void { @@ -69,17 +54,15 @@ function (Message $msg) use ($id, $deferred, &$listener): void { // Received a response $this->protocolReader->removeListener('message', $listener); if (SuccessResponse::isSuccessResponse($msg->body)) { - $deferred->resolve($msg->body->result); + $deferred->complete($msg->body->result); } else { - $deferred->fail($msg->body->error); + $deferred->error($msg->body->error); } } }; $this->protocolReader->on('message', $listener); - return $deferred->promise(); - }, - ); + return $deferred->getFuture()->await(); } /** @@ -88,7 +71,7 @@ function (Message $msg) use ($id, $deferred, &$listener): void { * @param string $method The method to call * @param array|object $params The method parameters */ - public function notify(string $method, $params): void + public function notify(string $method, array|object $params): void { $this->protocolWriter->write( new Message( diff --git a/src/Psalm/Internal/LanguageServer/EmitterInterface.php b/src/Psalm/Internal/LanguageServer/EmitterInterface.php index e331456fd11..35d66388a1b 100644 --- a/src/Psalm/Internal/LanguageServer/EmitterInterface.php +++ b/src/Psalm/Internal/LanguageServer/EmitterInterface.php @@ -47,7 +47,7 @@ public function on(string $eventName, callable $callBack, int $priority = 100): public function emit( string $eventName, array $arguments = [], - ?callable $continueCallBack = null + ?callable $continueCallBack = null, ): void; /** diff --git a/src/Psalm/Internal/LanguageServer/EmitterTrait.php b/src/Psalm/Internal/LanguageServer/EmitterTrait.php index c20ebe3b812..03f06a5b3d1 100644 --- a/src/Psalm/Internal/LanguageServer/EmitterTrait.php +++ b/src/Psalm/Internal/LanguageServer/EmitterTrait.php @@ -77,7 +77,7 @@ public function on(string $eventName, callable $callBack, int $priority = 100): public function emit( string $eventName, array $arguments = [], - ?callable $continueCallBack = null + ?callable $continueCallBack = null, ): void { if ($continueCallBack === null) { foreach ($this->listeners($eventName) as $listener) { diff --git a/src/Psalm/Internal/LanguageServer/LanguageClient.php b/src/Psalm/Internal/LanguageServer/LanguageClient.php index d20b17ec94e..12cdfc86c8b 100644 --- a/src/Psalm/Internal/LanguageServer/LanguageClient.php +++ b/src/Psalm/Internal/LanguageServer/LanguageClient.php @@ -4,7 +4,6 @@ namespace Psalm\Internal\LanguageServer; -use JsonMapper; use LanguageServerProtocol\LogMessage; use LanguageServerProtocol\LogTrace; use Psalm\Internal\LanguageServer\Client\Progress\LegacyProgress; @@ -12,11 +11,15 @@ use Psalm\Internal\LanguageServer\Client\Progress\ProgressInterface; use Psalm\Internal\LanguageServer\Client\TextDocument as ClientTextDocument; use Psalm\Internal\LanguageServer\Client\Workspace as ClientWorkspace; +use Revolt\EventLoop; +use Throwable; use function is_null; use function json_decode; use function json_encode; +use const JSON_THROW_ON_ERROR; + /** * @internal */ @@ -35,30 +38,24 @@ final class LanguageClient /** * The client handler */ - private ClientHandler $handler; - - /** - * The Language Server - */ - private LanguageServer $server; - - /** - * The Client Configuration - */ - public ClientConfiguration $clientConfiguration; + private readonly ClientHandler $handler; public function __construct( ProtocolReader $reader, ProtocolWriter $writer, - LanguageServer $server, - ClientConfiguration $clientConfiguration + /** + * The Language Server + */ + private readonly LanguageServer $server, + /** + * The Client Configuration + */ + public ClientConfiguration $clientConfiguration, ) { $this->handler = new ClientHandler($reader, $writer); - $this->server = $server; $this->textDocument = new ClientTextDocument($this->handler, $this->server); - $this->workspace = new ClientWorkspace($this->handler, new JsonMapper, $this->server); - $this->clientConfiguration = $clientConfiguration; + $this->workspace = new ClientWorkspace($this->handler, $this->server); } /** @@ -68,13 +65,13 @@ public function refreshConfiguration(): void { $capabilities = $this->server->clientCapabilities; if ($capabilities->workspace->configuration ?? false) { - $this->workspace->requestConfiguration('psalm')->onResolve(function ($error, $value): void { - if ($error) { - $this->server->logError('There was an error getting configuration'); - } else { - /** @var array $value */ - [$config] = $value; + EventLoop::queue(function (): void { + try { + /** @var object $config */ + [$config] = $this->workspace->requestConfiguration('psalm'); $this->configurationRefreshed((array) $config); + } catch (Throwable) { + $this->server->logError('There was an error getting configuration'); } }); } @@ -155,7 +152,7 @@ private function configurationRefreshed(array $config): void } /** @var array */ - $array = json_decode(json_encode($config), true); + $array = json_decode(json_encode($config, JSON_THROW_ON_ERROR), true, 512, JSON_THROW_ON_ERROR); if (isset($array['hideWarnings'])) { $this->clientConfiguration->hideWarnings = (bool) $array['hideWarnings']; diff --git a/src/Psalm/Internal/LanguageServer/LanguageServer.php b/src/Psalm/Internal/LanguageServer/LanguageServer.php index 54d15a4aa7c..d62cf404390 100644 --- a/src/Psalm/Internal/LanguageServer/LanguageServer.php +++ b/src/Psalm/Internal/LanguageServer/LanguageServer.php @@ -11,10 +11,6 @@ use AdvancedJsonRpc\Request; use AdvancedJsonRpc\Response; use AdvancedJsonRpc\SuccessResponse; -use Amp\Loop; -use Amp\Promise; -use Amp\Success; -use Generator; use InvalidArgumentException; use JsonMapper; use LanguageServerProtocol\ClientCapabilities; @@ -55,10 +51,9 @@ use Psalm\Internal\Provider\ProjectCacheProvider; use Psalm\Internal\Provider\Providers; use Psalm\IssueBuffer; +use Revolt\EventLoop; use Throwable; -use function Amp\asyncCoroutine; -use function Amp\call; use function array_combine; use function array_filter; use function array_keys; @@ -79,12 +74,13 @@ use function parse_url; use function rawurlencode; use function realpath; +use function str_contains; +use function str_ends_with; use function str_replace; use function stream_set_blocking; use function stream_socket_accept; use function stream_socket_client; use function stream_socket_server; -use function strpos; use function substr; use function trim; use function uniqid; @@ -113,27 +109,19 @@ final class LanguageServer extends Dispatcher public ?ClientInfo $clientInfo = null; - protected ProtocolReader $protocolReader; - - protected ProtocolWriter $protocolWriter; - public LanguageClient $client; public ?ClientCapabilities $clientCapabilities = null; public ?string $trace = null; - protected ProjectAnalyzer $project_analyzer; - - protected Codebase $codebase; - /** * The AMP Delay token */ - protected string $versionedAnalysisDelayToken = ''; + private string $versionedAnalysisDelayToken = ''; /** @var array}>> */ - protected array $issue_baseline = []; + private array $issue_baseline = []; /** * This should actually be a private property on `parent` @@ -142,97 +130,72 @@ final class LanguageServer extends Dispatcher */ protected JsonMapper $mapper; - protected PathMapper $path_mapper; - public function __construct( - ProtocolReader $reader, - ProtocolWriter $writer, - ProjectAnalyzer $project_analyzer, - Codebase $codebase, + protected ProtocolReader $protocolReader, + protected ProtocolWriter $protocolWriter, + protected ProjectAnalyzer $project_analyzer, + protected Codebase $codebase, ClientConfiguration $clientConfiguration, Progress $progress, - PathMapper $path_mapper + protected PathMapper $path_mapper, ) { parent::__construct($this, '/'); $progress->setServer($this); - - $this->project_analyzer = $project_analyzer; - - $this->codebase = $codebase; - - $this->path_mapper = $path_mapper; - - $this->protocolWriter = $writer; - - $this->protocolReader = $reader; $this->protocolReader->on( 'close', - function (): void { + function (): never { $this->shutdown(); $this->exit(); }, ); $this->protocolReader->on( 'message', - asyncCoroutine( - /** - * @return Generator - */ - function (Message $msg): Generator { - if (!$msg->body) { - return; - } + function (Message $msg): void { + if (!$msg->body) { + return; + } - // Ignore responses, this is the handler for requests and notifications - if (Response::isResponse($msg->body)) { - return; - } + // Ignore responses, this is the handler for requests and notifications + if (Response::isResponse($msg->body)) { + return; + } - $result = null; - $error = null; - try { - // Invoke the method handler to get a result - /** - * @var Promise|null - */ - $dispatched = $this->dispatch($msg->body); - if ($dispatched !== null) { - $result = yield $dispatched; - } else { - $result = null; - } - } catch (Error $e) { - // If a ResponseError is thrown, send it back in the Response - $error = $e; - } catch (Throwable $e) { - // If an unexpected error occurred, send back an INTERNAL_ERROR error response - $error = new Error( - (string) $e, - ErrorCode::INTERNAL_ERROR, - null, - $e, - ); - } + $result = null; + $error = null; + try { + // Invoke the method handler to get a result + $result = $this->dispatch($msg->body); + } catch (Error $e) { + // If a ResponseError is thrown, send it back in the Response + $error = $e; + } catch (Throwable $e) { + // If an unexpected error occurred, send back an INTERNAL_ERROR error response + $error = new Error( + (string) $e, + ErrorCode::INTERNAL_ERROR, + null, + $e, + ); + } + if ($error !== null) { + $this->logError($error->message); + } + // Only send a Response for a Request + // Notifications do not send Responses + /** + * @psalm-suppress UndefinedPropertyFetch + * @psalm-suppress MixedArgument + */ + if (Request::isRequest($msg->body)) { if ($error !== null) { - $this->logError($error->message); - } - // Only send a Response for a Request - // Notifications do not send Responses - /** - * @psalm-suppress UndefinedPropertyFetch - * @psalm-suppress MixedArgument - */ - if (Request::isRequest($msg->body)) { - if ($error !== null) { - $responseBody = new ErrorResponse($msg->body->id, $error); - } else { - $responseBody = new SuccessResponse($msg->body->id, $result); - } - yield $this->protocolWriter->write(new Message($responseBody)); + $responseBody = new ErrorResponse($msg->body->id, $error); + } else { + $responseBody = new SuccessResponse($msg->body->id, $result); } - }, - ), + $this->protocolWriter->write(new Message($responseBody)); + } + }, ); $this->protocolReader->on( @@ -243,7 +206,7 @@ static function (): void { }, ); - $this->client = new LanguageClient($reader, $writer, $this, $clientConfiguration); + $this->client = new LanguageClient($protocolReader, $protocolWriter, $this, $clientConfiguration); $this->logInfo("Psalm Language Server ".PSALM_VERSION." has started."); @@ -257,7 +220,7 @@ public static function run( ClientConfiguration $clientConfiguration, string $base_dir, PathMapper $path_mapper, - bool $inMemory = false + bool $inMemory = false, ): void { $progress = new Progress(); @@ -331,7 +294,7 @@ public static function run( $progress, $path_mapper, ); - Loop::run(); + EventLoop::run(); } elseif ($clientConfiguration->TCPServerMode && $clientConfiguration->TCPServerAddress) { // Run a TCP Server $tcpServer = stream_socket_server('tcp://' . $clientConfiguration->TCPServerAddress, $errno, $errstr); @@ -355,7 +318,7 @@ public static function run( $progress, $path_mapper, ); - Loop::run(); + EventLoop::run(); } } else { // Use STDIO @@ -369,7 +332,7 @@ public static function run( $progress, $path_mapper, ); - Loop::run(); + EventLoop::run(); } } @@ -382,106 +345,147 @@ public static function run( * @param ClientInfo|null $clientInfo Information about the client * @param string|null $trace The initial trace setting. If omitted trace is disabled ('off'). * @param string|null $workDoneToken The token to be used to report progress during init. - * @psalm-return Promise + * @psalm-return InitializeResult */ public function initialize( ClientCapabilities $capabilities, ?ClientInfo $clientInfo = null, ?string $rootUri = null, ?string $trace = null, - ?string $workDoneToken = null - ): Promise { + ?string $workDoneToken = null, + ): InitializeResult { $this->clientInfo = $clientInfo; $this->clientCapabilities = $capabilities; $this->trace = $trace; - if ($rootUri !== null) { $this->path_mapper->configureClientRoot($this->getPathPart($rootUri)); } - return call( - /** @return Generator */ - function () use ($workDoneToken) { - $progress = $this->client->makeProgress($workDoneToken ?? uniqid('tkn', true)); + $progress = $this->client->makeProgress($workDoneToken ?? uniqid('tkn', true)); - $this->logInfo("Initializing..."); - $progress->begin('Psalm', 'initializing'); + $this->logInfo("Initializing..."); + $progress->begin('Psalm', 'initializing'); - // Eventually, this might block on something. Leave it as a generator. - /** @psalm-suppress TypeDoesNotContainType */ - if (false) { - yield true; - } - - $this->project_analyzer->serverMode($this); - - $this->logInfo("Initializing: Getting code base..."); - $progress->update('getting code base'); - - $this->logInfo("Initializing: Scanning files ({$this->project_analyzer->threads} Threads)..."); - $progress->update('scanning files'); - $this->codebase->scanFiles($this->project_analyzer->threads); - - $this->logInfo("Initializing: Registering stub files..."); - $progress->update('registering stub files'); - $this->codebase->config->visitStubFiles($this->codebase, $this->project_analyzer->progress); - - if ($this->textDocument === null) { - $this->textDocument = new ServerTextDocument( - $this, - $this->codebase, - $this->project_analyzer, - ); - } - - if ($this->workspace === null) { - $this->workspace = new ServerWorkspace( - $this, - $this->codebase, - $this->project_analyzer, - ); - } + $this->project_analyzer->serverMode($this); - $serverCapabilities = new ServerCapabilities(); + $this->logInfo("Initializing: Getting code base..."); + $progress->update('getting code base'); - $textDocumentSyncOptions = new TextDocumentSyncOptions(); + $this->logInfo("Initializing: Scanning files ({$this->project_analyzer->threads} Threads)..."); + $progress->update('scanning files'); + $this->codebase->scanFiles($this->project_analyzer->threads); - //Open and close notifications are sent to the server. - $textDocumentSyncOptions->openClose = true; + $this->logInfo("Initializing: Registering stub files..."); + $progress->update('registering stub files'); + $this->codebase->config->visitStubFiles($this->codebase, $this->project_analyzer->progress); - $saveOptions = new SaveOptions(); - //The client is supposed to include the content on save. - $saveOptions->includeText = true; - $textDocumentSyncOptions->save = $saveOptions; + if ($this->textDocument === null) { + $this->textDocument = new ServerTextDocument( + $this, + $this->codebase, + $this->project_analyzer, + ); + } - /** - * Change notifications are sent to the server. See - * TextDocumentSyncKind.None, TextDocumentSyncKind.Full and - * TextDocumentSyncKind.Incremental. If omitted it defaults to - * TextDocumentSyncKind.None. - */ - if ($this->project_analyzer->onchange_line_limit === 0) { - /** - * Documents should not be synced at all. - */ - $textDocumentSyncOptions->change = TextDocumentSyncKind::NONE; - } else { - /** - * Documents are synced by always sending the full content - * of the document. - */ - $textDocumentSyncOptions->change = TextDocumentSyncKind::FULL; - } + if ($this->workspace === null) { + $this->workspace = new ServerWorkspace( + $this, + $this->codebase, + $this->project_analyzer, + ); + } - /** - * Defines how text documents are synced. Is either a detailed structure - * defining each notification or for backwards compatibility the - * TextDocumentSyncKind number. If omitted it defaults to - * `TextDocumentSyncKind.None`. - */ - $serverCapabilities->textDocumentSync = $textDocumentSyncOptions; + $serverCapabilities = new ServerCapabilities(); + + $textDocumentSyncOptions = new TextDocumentSyncOptions(); + + //Open and close notifications are sent to the server. + $textDocumentSyncOptions->openClose = true; + + $saveOptions = new SaveOptions(); + //The client is supposed to include the content on save. + $saveOptions->includeText = true; + $textDocumentSyncOptions->save = $saveOptions; + + /** + * Change notifications are sent to the server. See + * TextDocumentSyncKind.None, TextDocumentSyncKind.Full and + * TextDocumentSyncKind.Incremental. If omitted it defaults to + * TextDocumentSyncKind.None. + */ + if ($this->project_analyzer->onchange_line_limit === 0) { + /** + * Documents should not be synced at all. + */ + $textDocumentSyncOptions->change = TextDocumentSyncKind::NONE; + } else { + /** + * Documents are synced by always sending the full content + * of the document. + */ + $textDocumentSyncOptions->change = TextDocumentSyncKind::FULL; + } + /** + * Defines how text documents are synced. Is either a detailed structure + * defining each notification or for backwards compatibility the + * TextDocumentSyncKind number. If omitted it defaults to + * `TextDocumentSyncKind.None`. + */ + $serverCapabilities->textDocumentSync = $textDocumentSyncOptions; + + /** + * The server provides document symbol support. + * Support "Find all symbols" + */ + $serverCapabilities->documentSymbolProvider = false; + /** + * The server provides workspace symbol support. + * Support "Find all symbols in workspace" + */ + $serverCapabilities->workspaceSymbolProvider = false; + /** + * The server provides goto definition support. + * Support "Go to definition" + */ + $serverCapabilities->definitionProvider = true; + /** + * The server provides find references support. + * Support "Find all references" + */ + $serverCapabilities->referencesProvider = false; + /** + * The server provides hover support. + * Support "Hover" + */ + $serverCapabilities->hoverProvider = true; + + /** + * The server provides completion support. + * Support "Completion" + */ + if ($this->project_analyzer->provide_completion) { + $serverCapabilities->completionProvider = new CompletionOptions(); + /** + * The server provides support to resolve additional + * information for a completion item. + */ + $serverCapabilities->completionProvider->resolveProvider = false; + /** + * Most tools trigger completion request automatically without explicitly + * requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they + * do so when the user starts to type an identifier. For example if the user + * types `c` in a JavaScript file code complete will automatically pop up + * present `console` besides others as a completion item. Characters that + * make up identifiers don't need to be listed here. + * + * If code complete should automatically be trigger on characters not being + * valid inside an identifier (for example `.` in JavaScript) list them in + * `triggerCharacters`. + */ + $serverCapabilities->completionProvider->triggerCharacters = ['$', '>', ':',"[", "(", ",", " "]; + } /** * The server provides document symbol support. * Support "Find all symbols" @@ -513,71 +517,43 @@ function () use ($workDoneToken) { */ $serverCapabilities->documentHighlightProvider = false; - /** - * The server provides completion support. - * Support "Completion" - */ - if ($this->project_analyzer->provide_completion) { - $serverCapabilities->completionProvider = new CompletionOptions(); - /** - * The server provides support to resolve additional - * information for a completion item. - */ - $serverCapabilities->completionProvider->resolveProvider = false; - /** - * Most tools trigger completion request automatically without explicitly - * requesting it using a keyboard shortcut (e.g. Ctrl+Space). Typically they - * do so when the user starts to type an identifier. For example if the user - * types `c` in a JavaScript file code complete will automatically pop up - * present `console` besides others as a completion item. Characters that - * make up identifiers don't need to be listed here. - * - * If code complete should automatically be trigger on characters not being - * valid inside an identifier (for example `.` in JavaScript) list them in - * `triggerCharacters`. - */ - $serverCapabilities->completionProvider->triggerCharacters = ['$', '>', ':',"[", "(", ",", " "]; - } - - /** - * Whether code action supports the `data` property which is - * preserved between a `textDocument/codeAction` and a - * `codeAction/resolve` request. - * - * Support "Code Actions" if we support data - * - * @since LSP 3.16.0 - */ - if ($this->clientCapabilities->textDocument->publishDiagnostics->dataSupport ?? false) { - $serverCapabilities->codeActionProvider = true; - } + /** + * Whether code action supports the `data` property which is + * preserved between a `textDocument/codeAction` and a + * `codeAction/resolve` request. + * + * Support "Code Actions" if we support data + * + * @since LSP 3.16.0 + */ + if ($this->clientCapabilities->textDocument->publishDiagnostics->dataSupport ?? false) { + $serverCapabilities->codeActionProvider = true; + } - /** - * The server provides signature help support. - */ - $serverCapabilities->signatureHelpProvider = new SignatureHelpOptions(['(', ',']); + /** + * The server provides signature help support. + */ + $serverCapabilities->signatureHelpProvider = new SignatureHelpOptions(['(', ',']); - if ($this->client->clientConfiguration->baseline !== null) { - $this->logInfo('Utilizing Baseline: '.$this->client->clientConfiguration->baseline); - $this->issue_baseline= ErrorBaseline::read( - new FileProvider, - $this->client->clientConfiguration->baseline, - ); - } + if ($this->client->clientConfiguration->baseline !== null) { + $this->logInfo('Utilizing Baseline: '.$this->client->clientConfiguration->baseline); + $this->issue_baseline= ErrorBaseline::read( + new FileProvider, + $this->client->clientConfiguration->baseline, + ); + } - $this->logInfo("Initializing: Complete."); - $progress->end('initialized'); + $this->logInfo("Initializing: Complete."); + $progress->end('initialized'); - /** - * Information about the server. - * - * @since LSP 3.15.0 - */ - $initializeResultServerInfo = new InitializeResultServerInfo('Psalm Language Server', PSALM_VERSION); + /** + * Information about the server. + * + * @since LSP 3.15.0 + */ + $initializeResultServerInfo = new InitializeResultServerInfo('Psalm Language Server', PSALM_VERSION); - return new InitializeResult($serverCapabilities, $initializeResultServerInfo); - }, - ); + return new InitializeResult($serverCapabilities, $initializeResultServerInfo); } /** @@ -657,12 +633,12 @@ function (array $opened, string $file_path) { */ public function doVersionedAnalysisDebounce(array $files, ?int $version = null): void { - Loop::cancel($this->versionedAnalysisDelayToken); + EventLoop::cancel($this->versionedAnalysisDelayToken); if ($this->client->clientConfiguration->onChangeDebounceMs === null) { $this->doVersionedAnalysis($files, $version); } else { /** @psalm-suppress MixedAssignment,UnusedPsalmSuppress */ - $this->versionedAnalysisDelayToken = Loop::delay( + $this->versionedAnalysisDelayToken = EventLoop::delay( $this->client->clientConfiguration->onChangeDebounceMs, fn() => $this->doVersionedAnalysis($files, $version), ); @@ -676,7 +652,7 @@ public function doVersionedAnalysisDebounce(array $files, ?int $version = null): */ public function doVersionedAnalysis(array $files, ?int $version = null): void { - Loop::cancel($this->versionedAnalysisDelayToken); + EventLoop::cancel($this->versionedAnalysisDelayToken); try { $this->logDebug("Doing Analysis from version: $version"); $this->codebase->reloadFiles( @@ -733,14 +709,10 @@ function (IssueData $issue_data): Diagnostic { new Position($start_line - 1, $start_column - 1), new Position($end_line - 1, $end_column - 1), ); - switch ($severity) { - case IssueData::SEVERITY_INFO: - $diagnostic_severity = DiagnosticSeverity::WARNING; - break; - default: - $diagnostic_severity = DiagnosticSeverity::ERROR; - break; - } + $diagnostic_severity = match ($severity) { + IssueData::SEVERITY_INFO => DiagnosticSeverity::WARNING, + default => DiagnosticSeverity::ERROR, + }; $diagnostic = new Diagnostic( $description, $range, @@ -822,7 +794,7 @@ function (IssueData $issue_data) { * which they have sent a shutdown request. Clients should also wait with sending the exit notification until they * have received a response from the shutdown request. */ - public function shutdown(): Promise + public function shutdown(): void { $this->clientStatus('closing'); $this->logInfo("Shutting down..."); @@ -833,7 +805,6 @@ public function shutdown(): Promise $scanned_files, ); $this->clientStatus('closed'); - return new Success(null); } /** @@ -841,7 +812,7 @@ public function shutdown(): Promise * The server should exit with success code 0 if the shutdown request has been received before; * otherwise with error code 1. */ - public function exit(): void + public function exit(): never { exit(0); } @@ -872,7 +843,7 @@ public function log(int $type, string $message, array $context = []): void } if (!empty($context)) { - $message .= "\n" . json_encode($context, JSON_PRETTY_PRINT); + $message .= "\n" . (string) json_encode($context, JSON_PRETTY_PRINT); } try { $this->client->logMessage( @@ -881,7 +852,7 @@ public function log(int $type, string $message, array $context = []): void $message, ), ); - } catch (Throwable $err) { + } catch (Throwable) { // do nothing as we could potentially go into a loop here is not careful //TODO: Investigate if we can use error_log instead } @@ -944,7 +915,7 @@ private function clientStatus(string $status, ?string $additional_info = null): $status . (!empty($additional_info) ? ': ' . $additional_info : ''), ), ); - } catch (Throwable $err) { + } catch (Throwable) { // do nothing } } @@ -963,7 +934,7 @@ public function pathToUri(string $filepath): string $parts = explode('/', $filepath); // Don't %-encode the colon after a Windows drive letter $first = array_shift($parts); - if (substr($first, -1) !== ':') { + if (!str_ends_with($first, ':')) { $first = rawurlencode($first); } $parts = array_map('rawurlencode', $parts); @@ -980,7 +951,7 @@ public function uriToPath(string $uri): string { $filepath = urldecode($this->getPathPart($uri)); - if (strpos($filepath, ':') !== false) { + if (str_contains($filepath, ':')) { if ($filepath[0] === '/') { $filepath = substr($filepath, 1); } diff --git a/src/Psalm/Internal/LanguageServer/Message.php b/src/Psalm/Internal/LanguageServer/Message.php index 400cf9d0dbb..b0bae0c8655 100644 --- a/src/Psalm/Internal/LanguageServer/Message.php +++ b/src/Psalm/Internal/LanguageServer/Message.php @@ -5,6 +5,7 @@ namespace Psalm\Internal\LanguageServer; use AdvancedJsonRpc\Message as MessageBody; +use Stringable; use function array_pop; use function explode; @@ -13,10 +14,8 @@ /** * @internal */ -final class Message +final class Message implements Stringable { - public ?MessageBody $body = null; - /** * @var string[] */ @@ -45,9 +44,8 @@ public static function parse(string $msg): Message /** * @param string[] $headers */ - public function __construct(?MessageBody $body = null, array $headers = []) + public function __construct(public ?MessageBody $body = null, array $headers = []) { - $this->body = $body; if (!isset($headers['Content-Type'])) { $headers['Content-Type'] = 'application/vscode-jsonrpc; charset=utf8'; } diff --git a/src/Psalm/Internal/LanguageServer/PHPMarkdownContent.php b/src/Psalm/Internal/LanguageServer/PHPMarkdownContent.php index 0f954b257a3..b0d6303227a 100644 --- a/src/Psalm/Internal/LanguageServer/PHPMarkdownContent.php +++ b/src/Psalm/Internal/LanguageServer/PHPMarkdownContent.php @@ -17,18 +17,8 @@ */ final class PHPMarkdownContent extends MarkupContent implements JsonSerializable { - public string $code; - - public ?string $title = null; - - public ?string $description = null; - - public function __construct(string $code, ?string $title = null, ?string $description = null) + public function __construct(public string $code, public ?string $title = null, public ?string $description = null) { - $this->code = $code; - $this->title = $title; - $this->description = $description; - $markdown = ''; if ($title !== null) { $markdown = "**$title**\n\n"; @@ -38,18 +28,16 @@ public function __construct(string $code, ?string $title = null, ?string $descri } parent::__construct( MarkupKind::MARKDOWN, - "$markdown```php\nclient_root)) === $this->client_root) { + if (str_starts_with($client_path, $this->client_root)) { return $this->server_root . substr($client_path, strlen($this->client_root)); } @@ -44,7 +47,7 @@ public function mapServerToClient(string $server_path): string if ($this->client_root === null) { return $server_path; } - if (substr($server_path, 0, strlen($this->server_root)) === $this->server_root) { + if (str_starts_with($server_path, $this->server_root)) { return $this->client_root . substr($server_path, strlen($this->server_root)); } return $server_path; diff --git a/src/Psalm/Internal/LanguageServer/Progress.php b/src/Psalm/Internal/LanguageServer/Progress.php index 50f74472c00..2e138aefb3a 100644 --- a/src/Psalm/Internal/LanguageServer/Progress.php +++ b/src/Psalm/Internal/LanguageServer/Progress.php @@ -1,5 +1,7 @@ , ?string, void> - */ - function () use ($input): Generator { + $input = new ReadableResourceStream($input); + EventLoop::queue( + function () use ($input): void { while ($this->is_accepting_new_requests) { - $read_promise = $input->read(); - - $chunk = yield $read_promise; + $chunk = $input->read(); if ($chunk === null) { break; @@ -89,7 +82,7 @@ private function readMessages(string $buffer): int $this->parsing_mode = self::PARSE_BODY; $this->content_length = (int) ($this->headers['Content-Length'] ?? 0); $this->buffer = ''; - } elseif (substr($this->buffer, -2) === "\r\n") { + } elseif (str_ends_with($this->buffer, "\r\n")) { $parts = explode(':', $this->buffer); if (isset($parts[1])) { $this->headers[$parts[0]] = trim($parts[1]); @@ -108,7 +101,7 @@ private function readMessages(string $buffer): int // MessageBody::parse can throw an Error, maybe log an error? try { $msg = new Message(MessageBody::parse($this->buffer), $this->headers); - } catch (Exception $_) { + } catch (Exception) { $msg = null; } if ($msg) { diff --git a/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php b/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php index fe9cdd86a29..2010816fd6a 100644 --- a/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php +++ b/src/Psalm/Internal/LanguageServer/ProtocolStreamWriter.php @@ -4,29 +4,28 @@ namespace Psalm\Internal\LanguageServer; -use Amp\ByteStream\ResourceOutputStream; -use Amp\Promise; +use Amp\ByteStream\WritableResourceStream; /** * @internal */ final class ProtocolStreamWriter implements ProtocolWriter { - private ResourceOutputStream $output; + private readonly WritableResourceStream $output; /** * @param resource $output */ public function __construct($output) { - $this->output = new ResourceOutputStream($output); + $this->output = new WritableResourceStream($output); } /** * {@inheritdoc} */ - public function write(Message $msg): Promise + public function write(Message $msg): void { - return $this->output->write((string)$msg); + $this->output->write((string)$msg); } } diff --git a/src/Psalm/Internal/LanguageServer/ProtocolWriter.php b/src/Psalm/Internal/LanguageServer/ProtocolWriter.php index a512012a369..9f94d6f7aac 100644 --- a/src/Psalm/Internal/LanguageServer/ProtocolWriter.php +++ b/src/Psalm/Internal/LanguageServer/ProtocolWriter.php @@ -4,14 +4,10 @@ namespace Psalm\Internal\LanguageServer; -use Amp\Promise; - interface ProtocolWriter { /** - * Sends a Message to the client - * - * @return Promise Resolved when the message has been fully written out to the output stream + * Sends a Message to the client. */ - public function write(Message $msg): Promise; + public function write(Message $msg): void; } diff --git a/src/Psalm/Internal/LanguageServer/Provider/ClassLikeStorageCacheProvider.php b/src/Psalm/Internal/LanguageServer/Provider/ClassLikeStorageCacheProvider.php index 9f4ac2fdc81..3df8d6a7f63 100644 --- a/src/Psalm/Internal/LanguageServer/Provider/ClassLikeStorageCacheProvider.php +++ b/src/Psalm/Internal/LanguageServer/Provider/ClassLikeStorageCacheProvider.php @@ -1,5 +1,7 @@ loadFromCache($fq_classlike_name_lc); diff --git a/src/Psalm/Internal/LanguageServer/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/LanguageServer/Provider/FileReferenceCacheProvider.php index b4476c2e118..434013020c6 100644 --- a/src/Psalm/Internal/LanguageServer/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/LanguageServer/Provider/FileReferenceCacheProvider.php @@ -1,5 +1,7 @@ statements_cache[$file_path]) && $this->statements_cache_time[$file_path] >= $file_modified_time @@ -56,11 +58,7 @@ public function loadStatementsFromCache( */ public function loadExistingStatementsFromCache(string $file_path): ?array { - if (isset($this->statements_cache[$file_path])) { - return $this->statements_cache[$file_path]; - } - - return null; + return $this->statements_cache[$file_path] ?? null; } /** @@ -70,7 +68,7 @@ public function saveStatementsToCache( string $file_path, string $file_content_hash, array $stmts, - bool $touch_only + bool $touch_only, ): void { $this->statements_cache[$file_path] = $stmts; $this->statements_cache_time[$file_path] = microtime(true); @@ -79,11 +77,7 @@ public function saveStatementsToCache( public function loadExistingFileContentsFromCache(string $file_path): ?string { - if (isset($this->file_contents_cache[$file_path])) { - return $this->file_contents_cache[$file_path]; - } - - return null; + return $this->file_contents_cache[$file_path] ?? null; } public function cacheFileContents(string $file_path, string $file_contents): void diff --git a/src/Psalm/Internal/LanguageServer/Provider/ProjectCacheProvider.php b/src/Psalm/Internal/LanguageServer/Provider/ProjectCacheProvider.php index 80f758c83ca..ed85e5f4107 100644 --- a/src/Psalm/Internal/LanguageServer/Provider/ProjectCacheProvider.php +++ b/src/Psalm/Internal/LanguageServer/Provider/ProjectCacheProvider.php @@ -1,5 +1,7 @@ file_path = $file_path; - $this->symbol = $symbol; - $this->range = $range; } } diff --git a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php index a4af46cacec..7d35bd9cfab 100644 --- a/src/Psalm/Internal/LanguageServer/Server/TextDocument.php +++ b/src/Psalm/Internal/LanguageServer/Server/TextDocument.php @@ -4,8 +4,6 @@ namespace Psalm\Internal\LanguageServer\Server; -use Amp\Promise; -use Amp\Success; use LanguageServerProtocol\CodeAction; use LanguageServerProtocol\CodeActionContext; use LanguageServerProtocol\CodeActionKind; @@ -40,20 +38,11 @@ */ final class TextDocument { - protected LanguageServer $server; - - protected Codebase $codebase; - - protected ProjectAnalyzer $project_analyzer; - public function __construct( - LanguageServer $server, - Codebase $codebase, - ProjectAnalyzer $project_analyzer + protected LanguageServer $server, + protected Codebase $codebase, + protected ProjectAnalyzer $project_analyzer, ) { - $this->server = $server; - $this->codebase = $codebase; - $this->project_analyzer = $project_analyzer; } /** @@ -166,12 +155,11 @@ public function didClose(TextDocumentIdentifier $textDocument): void * * @param TextDocumentIdentifier $textDocument The text document * @param Position $position The position inside the text document - * @psalm-return Promise|Promise */ - public function definition(TextDocumentIdentifier $textDocument, Position $position): Promise + public function definition(TextDocumentIdentifier $textDocument, Position $position): ?Location { if (!$this->server->client->clientConfiguration->provideDefinition) { - return new Success(null); + return null; } $this->server->logDebug( @@ -182,34 +170,32 @@ public function definition(TextDocumentIdentifier $textDocument, Position $posit //This currently doesnt work right with out of project files if (!$this->codebase->config->isInProjectDirs($file_path)) { - return new Success(null); + return null; } try { $reference = $this->codebase->getReferenceAtPositionAsReference($file_path, $position); } catch (UnanalyzedFileException $e) { $this->server->logThrowable($e); - return new Success(null); + return null; } if ($reference === null) { - return new Success(null); + return null; } $code_location = $this->codebase->getSymbolLocationByReference($reference); if (!$code_location) { - return new Success(null); + return null; } - return new Success( - new Location( - $this->server->pathToUri($code_location->file_path), - new Range( - new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1), - new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1), - ), + return new Location( + $this->server->pathToUri($code_location->file_path), + new Range( + new Position($code_location->getLineNumber() - 1, $code_location->getColumn() - 1), + new Position($code_location->getEndLineNumber() - 1, $code_location->getEndColumn() - 1), ), ); } @@ -220,12 +206,11 @@ public function definition(TextDocumentIdentifier $textDocument, Position $posit * * @param TextDocumentIdentifier $textDocument The text document * @param Position $position The position inside the text document - * @psalm-return Promise|Promise */ - public function hover(TextDocumentIdentifier $textDocument, Position $position): Promise + public function hover(TextDocumentIdentifier $textDocument, Position $position): ?Hover { if (!$this->server->client->clientConfiguration->provideHover) { - return new Success(null); + return null; } $this->server->logDebug( @@ -236,32 +221,32 @@ public function hover(TextDocumentIdentifier $textDocument, Position $position): //This currently doesnt work right with out of project files if (!$this->codebase->config->isInProjectDirs($file_path)) { - return new Success(null); + return null; } try { $reference = $this->codebase->getReferenceAtPositionAsReference($file_path, $position); } catch (UnanalyzedFileException $e) { $this->server->logThrowable($e); - return new Success(null); + return null; } if ($reference === null) { - return new Success(null); + return null; } try { $markup = $this->codebase->getMarkupContentForSymbolByReference($reference); } catch (UnexpectedValueException $e) { $this->server->logThrowable($e); - return new Success(null); + return null; } if ($markup === null) { - return new Success(null); + return null; } - return new Success(new Hover($markup, $reference->range)); + return new Hover($markup, $reference->range); } /** @@ -276,12 +261,11 @@ public function hover(TextDocumentIdentifier $textDocument, Position $position): * * @param TextDocumentIdentifier $textDocument The text document * @param Position $position The position - * @psalm-return Promise>|Promise|Promise */ - public function completion(TextDocumentIdentifier $textDocument, Position $position): Promise + public function completion(TextDocumentIdentifier $textDocument, Position $position): ?CompletionList { if (!$this->server->client->clientConfiguration->provideCompletion) { - return new Success(null); + return null; } $this->server->logDebug( @@ -292,7 +276,7 @@ public function completion(TextDocumentIdentifier $textDocument, Position $posit //This currently doesnt work right with out of project files if (!$this->codebase->config->isInProjectDirs($file_path)) { - return new Success(null); + return null; } try { @@ -314,42 +298,36 @@ public function completion(TextDocumentIdentifier $textDocument, Position $posit $file_path, ); } - return new Success(new CompletionList($completion_items, false)); + return new CompletionList($completion_items, false); } - } catch (UnanalyzedFileException $e) { - $this->server->logThrowable($e); - return new Success(null); - } catch (TypeParseTreeException $e) { + } catch (UnanalyzedFileException|TypeParseTreeException $e) { $this->server->logThrowable($e); - return new Success(null); + return null; } try { $type_context = $this->codebase->getTypeContextAtPosition($file_path, $position); if ($type_context) { $completion_items = $this->codebase->getCompletionItemsForType($type_context); - return new Success(new CompletionList($completion_items, false)); + return new CompletionList($completion_items, false); } - } catch (UnexpectedValueException $e) { - $this->server->logThrowable($e); - return new Success(null); - } catch (TypeParseTreeException $e) { + } catch (UnexpectedValueException|TypeParseTreeException $e) { $this->server->logThrowable($e); - return new Success(null); + return null; } $this->server->logError('completion not found at ' . $position->line . ':' . $position->character); - return new Success(null); + return null; } /** * The signature help request is sent from the client to the server to request signature * information at a given cursor position. */ - public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): Promise + public function signatureHelp(TextDocumentIdentifier $textDocument, Position $position): ?SignatureHelp { if (!$this->server->client->clientConfiguration->provideSignatureHelp) { - return new Success(null); + return null; } $this->server->logDebug( @@ -360,37 +338,35 @@ public function signatureHelp(TextDocumentIdentifier $textDocument, Position $po //This currently doesnt work right with out of project files if (!$this->codebase->config->isInProjectDirs($file_path)) { - return new Success(null); + return null; } try { $argument_location = $this->codebase->getFunctionArgumentAtPosition($file_path, $position); } catch (UnanalyzedFileException $e) { $this->server->logThrowable($e); - return new Success(null); + return null; } if ($argument_location === null) { - return new Success(null); + return null; } try { $signature_information = $this->codebase->getSignatureInformation($argument_location[0], $file_path); } catch (UnexpectedValueException $e) { $this->server->logThrowable($e); - return new Success(null); + return null; } if (!$signature_information) { - return new Success(null); + return null; } - return new Success( - new SignatureHelp( - [$signature_information], - 0, - $argument_location[1], - ), + return new SignatureHelp( + [$signature_information], + 0, + $argument_location[1], ); } @@ -399,10 +375,10 @@ public function signatureHelp(TextDocumentIdentifier $textDocument, Position $po * for a given text document and range. These commands are typically code fixes to * either fix problems or to beautify/refactor code. */ - public function codeAction(TextDocumentIdentifier $textDocument, CodeActionContext $context): Promise + public function codeAction(TextDocumentIdentifier $textDocument, CodeActionContext $context): ?array { if (!$this->server->client->clientConfiguration->provideCodeActions) { - return new Success(null); + return null; } $this->server->logDebug( @@ -413,7 +389,7 @@ public function codeAction(TextDocumentIdentifier $textDocument, CodeActionConte //Don't report code actions for files we arent watching if (!$this->codebase->config->isInProjectDirs($file_path)) { - return new Success(null); + return null; } $fixers = []; @@ -458,11 +434,9 @@ public function codeAction(TextDocumentIdentifier $textDocument, CodeActionConte } if (empty($fixers)) { - return new Success(null); + return null; } - return new Success( - array_values($fixers), - ); + return array_values($fixers); } } diff --git a/src/Psalm/Internal/LanguageServer/Server/Workspace.php b/src/Psalm/Internal/LanguageServer/Server/Workspace.php index 75c810cf800..a4f3aef6ca4 100644 --- a/src/Psalm/Internal/LanguageServer/Server/Workspace.php +++ b/src/Psalm/Internal/LanguageServer/Server/Workspace.php @@ -4,8 +4,6 @@ namespace Psalm\Internal\LanguageServer\Server; -use Amp\Promise; -use Amp\Success; use InvalidArgumentException; use LanguageServerProtocol\FileChangeType; use LanguageServerProtocol\FileEvent; @@ -27,20 +25,11 @@ */ final class Workspace { - protected LanguageServer $server; - - protected Codebase $codebase; - - protected ProjectAnalyzer $project_analyzer; - public function __construct( - LanguageServer $server, - Codebase $codebase, - ProjectAnalyzer $project_analyzer + protected LanguageServer $server, + protected Codebase $codebase, + protected ProjectAnalyzer $project_analyzer, ) { - $this->server = $server; - $this->codebase = $codebase; - $this->project_analyzer = $project_analyzer; } /** @@ -64,7 +53,7 @@ public function didChangeWatchedFiles(array $changes): void array_map(function (FileEvent $change) { try { return $this->server->uriToPath($change->uri); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return null; } }, $changes), @@ -108,9 +97,9 @@ public function didChangeWatchedFiles(array $changes): void /** * A notification sent from the client to the server to signal the change of configuration settings. * - * @psalm-suppress PossiblyUnusedMethod + * @psalm-suppress PossiblyUnusedMethod, UnusedParam */ - public function didChangeConfiguration(): void + public function didChangeConfiguration(mixed $settings): void { $this->server->logDebug( 'workspace/didChangeConfiguration', @@ -122,10 +111,9 @@ public function didChangeConfiguration(): void * The workspace/executeCommand request is sent from the client to the server to * trigger command execution on the server. * - * @param mixed $arguments * @psalm-suppress PossiblyUnusedMethod */ - public function executeCommand(string $command, $arguments): Promise + public function executeCommand(string $command, mixed $arguments): void { $this->server->logDebug( 'workspace/executeCommand', @@ -154,7 +142,5 @@ public function executeCommand(string $command, $arguments): Promise $this->server->emitVersionedIssues([$file => $arguments['uri']]); break; } - - return new Success(null); } } diff --git a/src/Psalm/Internal/MethodIdentifier.php b/src/Psalm/Internal/MethodIdentifier.php index 62742a74b9f..b50a9f9f316 100644 --- a/src/Psalm/Internal/MethodIdentifier.php +++ b/src/Psalm/Internal/MethodIdentifier.php @@ -1,47 +1,43 @@ fq_class_name = $fq_class_name; - $this->method_name = $method_name; } /** * Takes any valid reference to a method id and converts * it into a MethodIdentifier * - * @param string|MethodIdentifier $method_id * @psalm-pure */ - public static function wrap($method_id): self + public static function wrap(string|MethodIdentifier $method_id): self { return is_string($method_id) ? static::fromMethodIdReference($method_id) : $method_id; } @@ -51,7 +47,7 @@ public static function wrap($method_id): self */ public static function isValidMethodIdReference(string $method_id): bool { - return strpos($method_id, '::') !== false; + return str_contains($method_id, '::'); } /** diff --git a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php index f1e2673572d..2f32f5c6379 100644 --- a/src/Psalm/Internal/PhpTraverser/CustomTraverser.php +++ b/src/Psalm/Internal/PhpTraverser/CustomTraverser.php @@ -27,9 +27,8 @@ public function __construct() * Recursively traverse a node. * * @param Node $node node to traverse - * @return Node Result of traversal (may be original node or new one) */ - protected function traverseNode(Node $node): Node + protected function traverseNode(Node $node): void { foreach ($node->getSubNodeNames() as $name) { $subNode = &$node->$name; @@ -60,7 +59,7 @@ protected function traverseNode(Node $node): Node } if ($traverseChildren) { - $subNode = $this->traverseNode($subNode); + $this->traverseNode($subNode); if ($this->stopTraversal) { break; } @@ -88,8 +87,6 @@ protected function traverseNode(Node $node): Node } } } - - return $node; } /** @@ -124,7 +121,7 @@ protected function traverseArray(array $nodes): array } if ($traverseChildren) { - $node = $this->traverseNode($node); + $this->traverseNode($node); if ($this->stopTraversal) { break; } @@ -147,7 +144,7 @@ protected function traverseArray(array $nodes): array } elseif (false === $return) { throw new LogicException( 'bool(false) return from leaveNode() no longer supported. ' . - 'Return NodeTraverser::REMOVE_NODE instead', + 'Return NodeVisitor::REMOVE_NODE instead', ); } else { throw new LogicException( diff --git a/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php b/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php index 28b3770c167..440e1aa2db8 100644 --- a/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/AssignmentMapVisitor.php @@ -1,5 +1,7 @@ > */ - protected array $assignment_map = []; - - protected ?string $this_class_name = null; + private array $assignment_map = []; - public function __construct(?string $this_class_name) + public function __construct(protected ?string $this_class_name) { - $this->this_class_name = $this_class_name; } public function enterNode(PhpParser\Node $node): ?int @@ -52,7 +51,7 @@ public function enterNode(PhpParser\Node $node): ?int } } - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + return PhpParser\NodeVisitor::DONT_TRAVERSE_CHILDREN; } if ($node instanceof PhpParser\Node\Expr\PostInc @@ -67,7 +66,7 @@ public function enterNode(PhpParser\Node $node): ?int $this->assignment_map[$var_id][$var_id] = true; } - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + return PhpParser\NodeVisitor::DONT_TRAVERSE_CHILDREN; } if ($node instanceof PhpParser\Node\Expr\FuncCall diff --git a/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php b/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php index 4179ac0d0e6..ce4da4e2191 100644 --- a/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/CheckTrivialExprVisitor.php @@ -1,5 +1,7 @@ checkNonTrivialExpr($node)) { $this->has_non_trivial_expr = true; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } if ($node instanceof PhpParser\Node\Expr\ClassConstFetch @@ -61,8 +62,11 @@ public function enterNode(PhpParser\Node $node): ?int || $node instanceof PhpParser\Node\Expr\Error || $node instanceof PhpParser\Node\Expr\PropertyFetch || $node instanceof PhpParser\Node\Expr\StaticPropertyFetch) { - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } + } elseif ($node instanceof PhpParser\Node\ClosureUse) { + $this->has_non_trivial_expr = true; + return self::STOP_TRAVERSAL; } return null; } diff --git a/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php b/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php index c0c4d3e3f5a..02c3ec4cb24 100644 --- a/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ConditionCloningVisitor.php @@ -14,11 +14,9 @@ */ final class ConditionCloningVisitor extends NodeVisitorAbstract { - private NodeDataProvider $type_provider; - - public function __construct(NodeDataProvider $old_type_provider) - { - $this->type_provider = $old_type_provider; + public function __construct( + private readonly NodeDataProvider $type_provider, + ) { } /** diff --git a/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php b/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php index d7a9ab78dbc..936a58080a6 100644 --- a/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/NodeCleanerVisitor.php @@ -1,5 +1,7 @@ type_provider = $type_provider; + public function __construct( + private readonly NodeDataProvider $type_provider, + ) { } public function enterNode(PhpParser\Node $node): ?int diff --git a/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php b/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php index c889bd2fe42..9a5f446974c 100644 --- a/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/NodeCounterVisitor.php @@ -1,5 +1,7 @@ */ - private array $extra_offsets; - /** * @param array $extra_offsets */ - public function __construct(int $offset, int $line_offset, array $extra_offsets) - { - $this->file_offset = $offset; - $this->line_offset = $line_offset; - $this->extra_offsets = $extra_offsets; + public function __construct( + private readonly int $file_offset, + private readonly int $line_offset, + private array $extra_offsets, + ) { } public function enterNode(PhpParser\Node $node): ?int diff --git a/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php b/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php index ce3574db13c..a09b2c1eb53 100644 --- a/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ParamReplacementVisitor.php @@ -1,5 +1,7 @@ */ private array $replacements = []; @@ -27,10 +25,10 @@ final class ParamReplacementVisitor extends PhpParser\NodeVisitorAbstract private bool $new_new_name_used = false; - public function __construct(string $old_name, string $new_name) - { - $this->old_name = $old_name; - $this->new_name = $new_name; + public function __construct( + private readonly string $old_name, + private readonly string $new_name, + ) { } public function enterNode(PhpParser\Node $node): ?int @@ -45,7 +43,7 @@ public function enterNode(PhpParser\Node $node): ?int } elseif ($node->name === $this->new_name) { if ($this->new_new_name_used) { $this->replacements = []; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } $this->replacements[] = new FileManipulation( @@ -58,7 +56,7 @@ public function enterNode(PhpParser\Node $node): ?int } elseif ($node->name === $this->new_name . '_new') { if ($this->new_name_replaced) { $this->replacements = []; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } $this->new_new_name_used = true; diff --git a/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php b/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php index 59343c14c1b..d185eadea03 100644 --- a/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/PartialParserVisitor.php @@ -1,11 +1,14 @@ */ - private array $offset_map; - private bool $must_rescan = false; private int $non_method_changes; - private string $a_file_contents; - - private string $b_file_contents; - - private int $a_file_contents_length; - - private Parser $parser; - - private Collecting $error_handler; + private readonly int $a_file_contents_length; /** @param array $offset_map */ public function __construct( - Parser $parser, - Collecting $error_handler, - array $offset_map, - string $a_file_contents, - string $b_file_contents + private readonly Parser $parser, + private readonly Collecting $error_handler, + private readonly array $offset_map, + private readonly string $a_file_contents, + private readonly string $b_file_contents, ) { - $this->parser = $parser; - $this->error_handler = $error_handler; - $this->offset_map = $offset_map; - $this->a_file_contents = $a_file_contents; $this->a_file_contents_length = strlen($a_file_contents); - $this->b_file_contents = $b_file_contents; $this->non_method_changes = count($offset_map); } - /** - * @return null|int|PhpParser\Node - */ - public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true) + public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true): int|PhpParser\Node|null { /** @var array{startFilePos: int, endFilePos: int, startLine: int} */ $attrs = $node->getAttributes(); @@ -119,7 +103,7 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true) if ($a_e2 > $stmt_end_pos) { $this->must_rescan = true; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } $end_offset = $b_e2 - $a_e2; @@ -155,7 +139,7 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true) if (!$method_contents) { $this->must_rescan = true; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } $error_handler = new Collecting(); @@ -220,11 +204,15 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true) ) { $this->must_rescan = true; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } // changes "): {" to ") {" - $hacky_class_fix = preg_replace('/(\)[\s]*):([\s]*\{)/', '$1 $2', $hacky_class_fix); + $hacky_class_fix = (string) preg_replace( + '/(\)[\s]*):([\s]*\{)/', + '$1 $2', + $hacky_class_fix, + ); if ($hacky_class_fix !== $fake_class) { $replacement_stmts = $this->parser->parse( @@ -239,7 +227,7 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true) ) { $this->must_rescan = true; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } } @@ -291,12 +279,14 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true) $traverseChildren = false; + assert(!empty($replacement_stmts)); + return reset($replacement_stmts); } $this->must_rescan = true; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } if ($node->stmts) { @@ -327,7 +317,7 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true) $this->must_rescan = true; - return PhpParser\NodeTraverser::STOP_TRAVERSAL; + return self::STOP_TRAVERSAL; } if ($start_offset !== 0 || $end_offset !== 0 || $line_offset !== 0) { diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php index 63ec7c07293..db63d7e2ddc 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/AttributeResolver.php @@ -1,5 +1,7 @@ name instanceof PhpParser\Node\Name\FullyQualified) { $fq_type_string = (string)$stmt->name; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php index 2a6922627c2..608b741ba5d 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeDocblockParser.php @@ -1,5 +1,7 @@ getCodebase(); @@ -66,9 +69,9 @@ public static function parse( $templates = []; if (isset($parsed_docblock->combined_tags['template'])) { foreach ($parsed_docblock->combined_tags['template'] as $offset => $template_line) { - $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); + $template_type = preg_split('/[\s]+/', CommentAnalyzer::sanitizeDocblockType($template_line)); if ($template_type === false) { - throw new IncorrectDocblockException('Invalid @ŧemplate tag: '.preg_last_error_msg()); + throw new IncorrectDocblockException('Invalid @template tag: '.preg_last_error_msg()); } $template_name = array_shift($template_type); @@ -109,7 +112,7 @@ public static function parse( if (isset($parsed_docblock->combined_tags['template-covariant'])) { foreach ($parsed_docblock->combined_tags['template-covariant'] as $offset => $template_line) { - $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); + $template_type = preg_split('/[\s]+/', CommentAnalyzer::sanitizeDocblockType($template_line)); if ($template_type === false) { throw new IncorrectDocblockException('Invalid @template-covariant tag: '.preg_last_error_msg()); } @@ -169,20 +172,16 @@ public static function parse( if (isset($parsed_docblock->tags['psalm-require-extends']) && count($extension_requirements = $parsed_docblock->tags['psalm-require-extends']) > 0) { - $info->extension_requirement = trim(preg_replace( - '@^[ \t]*\*@m', - '', + $info->extension_requirement = CommentAnalyzer::sanitizeDocblockType( $extension_requirements[array_key_first($extension_requirements)], - )); + ); } if (isset($parsed_docblock->tags['psalm-require-implements'])) { foreach ($parsed_docblock->tags['psalm-require-implements'] as $implementation_requirement) { - $info->implementation_requirements[] = trim(preg_replace( - '@^[ \t]*\*@m', - '', + $info->implementation_requirements[] = CommentAnalyzer::sanitizeDocblockType( $implementation_requirement, - )); + ); } } @@ -195,9 +194,9 @@ public static function parse( } if (isset($parsed_docblock->tags['psalm-yield'])) { - $yield = reset($parsed_docblock->tags['psalm-yield']); + $yield = (string) reset($parsed_docblock->tags['psalm-yield']); - $info->yield = trim(preg_replace('@^[ \t]*\*@m', '', $yield)); + $info->yield = CommentAnalyzer::sanitizeDocblockType($yield); } if (isset($parsed_docblock->tags['deprecated'])) { @@ -238,18 +237,20 @@ public static function parse( } } - if (isset($parsed_docblock->tags['psalm-seal-properties'])) { - $info->sealed_properties = true; - } - if (isset($parsed_docblock->tags['psalm-no-seal-properties'])) { - $info->sealed_properties = false; - } + foreach (['', 'psalm-'] as $prefix) { + if (isset($parsed_docblock->tags[$prefix . 'seal-properties'])) { + $info->sealed_properties = true; + } + if (isset($parsed_docblock->tags[$prefix . 'no-seal-properties'])) { + $info->sealed_properties = false; + } - if (isset($parsed_docblock->tags['psalm-seal-methods'])) { - $info->sealed_methods = true; - } - if (isset($parsed_docblock->tags['psalm-no-seal-methods'])) { - $info->sealed_methods = false; + if (isset($parsed_docblock->tags[$prefix . 'seal-methods'])) { + $info->sealed_methods = true; + } + if (isset($parsed_docblock->tags[$prefix . 'no-seal-methods'])) { + $info->sealed_methods = false; + } } if (isset($parsed_docblock->tags['psalm-inheritors'])) { @@ -314,7 +315,7 @@ public static function parse( $info->sealed_methods = true; } foreach ($parsed_docblock->combined_tags['method'] as $offset => $method_entry) { - $method_entry = preg_replace('/[ \t]+/', ' ', trim($method_entry)); + $method_entry = (string) preg_replace('/[ \t]+/', ' ', trim($method_entry)); $docblock_lines = []; @@ -343,9 +344,9 @@ public static function parse( } } - $method_entry = trim(preg_replace('/\/\/.*/', '', $method_entry)); + $method_entry = trim((string) preg_replace('/\/\/.*/', '', $method_entry)); - $method_entry = preg_replace( + $method_entry = (string) preg_replace( '/array\(([0-9a-zA-Z_\'\" ]+,)*([0-9a-zA-Z_\'\" ]+)\)/', '[]', $method_entry, @@ -358,10 +359,14 @@ public static function parse( } $method_entry = str_replace([', ', '( '], [',', '('], $method_entry); - $method_entry = preg_replace('/ (?!(\$|\.\.\.|&))/', '', trim($method_entry)); + $method_entry = (string) preg_replace('/ (?!(\$|\.\.\.|&))/', '', trim($method_entry)); // replace array bracket contents - $method_entry = preg_replace('/\[([0-9a-zA-Z_\'\" ]+,)*([0-9a-zA-Z_\'\" ]+)\]/', '[]', $method_entry); + $method_entry = (string) preg_replace( + '/\[([0-9a-zA-Z_\'\" ]+,)*([0-9a-zA-Z_\'\" ]+)\]/', + '[]', + $method_entry, + ); if (!$method_entry) { throw new DocblockParseException('No @method entry specified'); @@ -453,7 +458,7 @@ public static function parse( $codebase->analysis_php_version_id, $has_errors, ); - } catch (Exception $e) { + } catch (Exception) { throw new DocblockParseException('Badly-formatted @method string ' . $method_entry); } @@ -526,11 +531,11 @@ public static function parse( * 'psalm-property-read'|'property-write'|'psalm-property-write' $property_tag * @throws DocblockParseException */ - protected static function addMagicPropertyToInfo( + private static function addMagicPropertyToInfo( Doc $comment, ClassLikeDocblockComment $info, array $specials, - string $property_tag + string $property_tag, ): void { $magic_property_comments = $specials[$property_tag] ?? []; @@ -547,11 +552,11 @@ protected static function addMagicPropertyToInfo( ) { $line_parts[1] = str_replace('&', '', $line_parts[1]); - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); + $line_parts[1] = (string) preg_replace('/,$/', '', $line_parts[1], 1); $end = $offset + strlen($line_parts[0]); - $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); + $line_parts[0] = CommentAnalyzer::sanitizeDocblockType($line_parts[0]); if ($line_parts[0] === '' || ($line_parts[0][0] === '$' @@ -592,7 +597,7 @@ private static function getMethodOffset(Doc $comment, string $method_entry): int $method_offset = 0; foreach ($lines as $i => $line) { - if (strpos($line, $method_entry) !== false) { + if (str_contains($line, $method_entry)) { $method_offset = $i; break; } diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php index dd97adf9b80..6a503023079 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -1,5 +1,7 @@ @@ -116,10 +109,6 @@ final class ClassLikeNodeScanner */ public array $class_template_types = []; - private ?Name $namespace_name = null; - - private Aliases $aliases; - public ?ClassLikeStorage $storage = null; /** @@ -128,19 +117,14 @@ final class ClassLikeNodeScanner public array $type_aliases = []; public function __construct( - Codebase $codebase, - FileStorage $file_storage, - FileScanner $file_scanner, - Aliases $aliases, - ?Name $namespace_name + private readonly Codebase $codebase, + private readonly FileStorage $file_storage, + private readonly FileScanner $file_scanner, + private readonly Aliases $aliases, + private readonly ?Name $namespace_name, ) { - $this->codebase = $codebase; - $this->file_storage = $file_storage; - $this->file_scanner = $file_scanner; $this->file_path = $file_storage->file_path; - $this->aliases = $aliases; $this->config = Config::getInstance(); - $this->namespace_name = $namespace_name; } /** @@ -225,7 +209,7 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool foreach ($storage->dependent_classlikes as $dependent_name_lc => $_) { try { $dependent_storage = $this->codebase->classlike_storage_provider->get($dependent_name_lc); - } catch (InvalidArgumentException $exception) { + } catch (InvalidArgumentException) { continue; } $dependent_storage->populated = false; @@ -516,7 +500,7 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool ); $storage->yield = $yield_type; - } catch (TypeParseTreeException $e) { + } catch (TypeParseTreeException) { // do nothing } } @@ -750,16 +734,15 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool if ($storage->is_enum) { $name_types = []; $values_types = []; - foreach ($storage->enum_cases as $name => $enumCaseStorage) { + foreach ($storage->enum_cases as $name => $enum_case_storage) { $name_types[] = Type::getAtomicStringFromLiteral($name); - if ($storage->enum_type !== null) { - if (is_string($enumCaseStorage->value)) { - $values_types[] = Type::getAtomicStringFromLiteral($enumCaseStorage->value); - } elseif (is_int($enumCaseStorage->value)) { - $values_types[] = new Type\Atomic\TLiteralInt($enumCaseStorage->value); - } elseif ($enumCaseStorage->value instanceof UnresolvedConstantComponent) { + if ($storage->enum_type !== null + && $enum_case_storage->value !== null) { + if ($enum_case_storage->value instanceof UnresolvedConstantComponent) { // backed enum with a type yet unknown $values_types[] = new Type\Atomic\TMixed; + } else { + $values_types[] = $enum_case_storage->value; } } } @@ -886,7 +869,7 @@ public function finish(PhpParser\Node\Stmt\ClassLike $node): ClassLikeStorage '@psalm-type ' . $key . ' contains invalid reference: ' . $e->getMessage(), new CodeLocation($this->file_scanner, $node, null, true), ); - } catch (Exception $e) { + } catch (Exception) { $classlike_storage->docblock_issues[] = new InvalidDocblock( '@psalm-type ' . $key . ' contains invalid references', new CodeLocation($this->file_scanner, $node, null, true), @@ -958,7 +941,7 @@ public function handleTraitUse(PhpParser\Node\Stmt\TraitUse $node): void $this->useTemplatedType( $storage, $node, - trim(preg_replace('@^[ \t]*\*@m', '', $template_line)), + CommentAnalyzer::sanitizeDocblockType($template_line), ); } } @@ -979,7 +962,7 @@ public function handleTraitUse(PhpParser\Node\Stmt\TraitUse $node): void private function extendTemplatedType( ClassLikeStorage $storage, PhpParser\Node\Stmt\ClassLike $node, - string $extended_class_name + string $extended_class_name, ): void { if (trim($extended_class_name) === '') { $storage->docblock_issues[] = new InvalidDocblock( @@ -1063,7 +1046,7 @@ private function extendTemplatedType( private function implementTemplatedType( ClassLikeStorage $storage, PhpParser\Node\Stmt\ClassLike $node, - string $implemented_class_name + string $implemented_class_name, ): void { if (trim($implemented_class_name) === '') { $storage->docblock_issues[] = new InvalidDocblock( @@ -1149,7 +1132,7 @@ private function implementTemplatedType( private function useTemplatedType( ClassLikeStorage $storage, PhpParser\Node\Stmt\TraitUse $node, - string $used_class_name + string $used_class_name, ): void { if (trim($used_class_name) === '') { $storage->docblock_issues[] = new InvalidDocblock( @@ -1265,7 +1248,7 @@ private static function registerEmptyConstructor(ClassLikeStorage $class_storage private function visitClassConstDeclaration( PhpParser\Node\Stmt\ClassConst $stmt, ClassLikeStorage $storage, - string $fq_classlike_name + string $fq_classlike_name, ): void { if ($storage->is_trait && $this->codebase->analysis_php_version_id < 8_02_00) { IssueBuffer::maybeAdd(new ConstantDeclarationInTrait( @@ -1375,7 +1358,7 @@ private function visitClassConstDeclaration( && !( $const->value instanceof Concat && $inferred_type->isSingle() - && get_class($inferred_type->getSingleAtomic()) === TString::class + && $inferred_type->getSingleAtomic()::class === TString::class ) ) { $exists = true; @@ -1445,7 +1428,7 @@ private function visitClassConstDeclaration( private function visitEnumDeclaration( PhpParser\Node\Stmt\EnumCase $stmt, ClassLikeStorage $storage, - string $fq_classlike_name + string $fq_classlike_name, ): void { if (isset($storage->constants[$stmt->name->name])) { IssueBuffer::maybeAdd(new DuplicateConstant( @@ -1473,9 +1456,9 @@ private function visitEnumDeclaration( if ($case_type) { if ($case_type->isSingleIntLiteral()) { - $enum_value = $case_type->getSingleIntLiteral()->value; + $enum_value = $case_type->getSingleIntLiteral(); } elseif ($case_type->isSingleStringLiteral()) { - $enum_value = $case_type->getSingleStringLiteral()->value; + $enum_value = $case_type->getSingleStringLiteral(); } else { IssueBuffer::maybeAdd( new InvalidEnumCaseValue( @@ -1550,7 +1533,7 @@ private function getAttributeStorageFromStatement( FileStorage $file_storage, Aliases $aliases, PhpParser\Node\Stmt $stmt, - ?string $fq_classlike_name + ?string $fq_classlike_name, ): array { $storages = []; foreach ($stmt->attrGroups as $attr_group) { @@ -1575,7 +1558,7 @@ private function visitPropertyDeclaration( PhpParser\Node\Stmt\Property $stmt, Config $config, ClassLikeStorage $storage, - string $fq_classlike_name + string $fq_classlike_name, ): void { $comment = $stmt->getDocComment(); $var_comment = null; @@ -1655,6 +1638,16 @@ private function visitPropertyDeclaration( foreach ($stmt->props as $property) { $doc_var_location = null; + if (isset($storage->properties[$property->name->name])) { + IssueBuffer::maybeAdd( + new DuplicateProperty( + 'Property ' . $fq_classlike_name . '::$' . $property->name->name . ' has already been defined', + new CodeLocation($this->file_scanner, $stmt, null, true), + $fq_classlike_name . '::$' . $property->name->name, + ), + ); + } + $property_storage = $storage->properties[$property->name->name] = new PropertyStorage(); $property_storage->is_static = $stmt->isStatic(); $property_storage->type = $signature_type; @@ -1899,7 +1892,7 @@ public static function getTypeAliasesFromComment( PhpParser\Comment\Doc $comment, Aliases $aliases, ?array $type_aliases, - ?string $self_fqcln + ?string $self_fqcln, ): array { $parsed_docblock = DocComment::parsePreservingLength($comment); @@ -1930,7 +1923,7 @@ private static function getTypeAliasesFromCommentLines( array $type_alias_comment_lines, Aliases $aliases, ?array $type_aliases, - ?string $self_fqcln + ?string $self_fqcln, ): array { $type_alias_tokens = []; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php index cf5dd8a0519..da64d4115b1 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionResolver.php @@ -1,5 +1,7 @@ value); } @@ -339,7 +341,7 @@ public static function getUnresolvedClassConstExpr( public static function enterConditional( Codebase $codebase, string $file_path, - PhpParser\Node\Expr $expr + PhpParser\Node\Expr $expr, ): ?bool { if ($expr instanceof PhpParser\Node\Expr\BooleanNot) { $enter_negated = self::enterConditional($codebase, $file_path, $expr->expr); @@ -371,11 +373,11 @@ public static function enterConditional( ( $expr->left instanceof PhpParser\Node\Expr\ConstFetch && $expr->left->name->getParts() === ['PHP_VERSION_ID'] - && $expr->right instanceof PhpParser\Node\Scalar\LNumber + && $expr->right instanceof PhpParser\Node\Scalar\Int_ ) || ( $expr->right instanceof PhpParser\Node\Expr\ConstFetch && $expr->right->name->getParts() === ['PHP_VERSION_ID'] - && $expr->left instanceof PhpParser\Node\Scalar\LNumber + && $expr->left instanceof PhpParser\Node\Scalar\Int_ ) ) ) { @@ -388,7 +390,7 @@ public static function enterConditional( }); try { return (bool) $evaluator->evaluateSilently($expr); - } catch (ConstExprEvaluationException $e) { + } catch (ConstExprEvaluationException) { return null; } } @@ -404,7 +406,7 @@ public static function enterConditional( private static function functionEvaluatesToTrue( Codebase $codebase, string $file_path, - PhpParser\Node\Expr\FuncCall $function + PhpParser\Node\Expr\FuncCall $function, ): ?bool { if (!$function->name instanceof PhpParser\Node\Name) { return null; diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php index 6e7887ff158..9ccbd8e6c61 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ExpressionScanner.php @@ -1,5 +1,7 @@ params_out[] = [ 'name' => trim($line_parts[1]), @@ -193,10 +197,10 @@ public static function parse( $line_parts = CommentAnalyzer::splitDocLine($param); if (count($line_parts) > 0) { - $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); + $line_parts[0] = CommentAnalyzer::sanitizeDocblockType($line_parts[0]); $info->self_out = [ - 'type' => str_replace("\n", '', $line_parts[0]), + 'type' => $line_parts[0], 'line_number' => $comment->getStartLine() + substr_count( $comment_text, "\n", @@ -220,10 +224,10 @@ public static function parse( foreach ($parsed_docblock->tags['psalm-if-this-is'] as $offset => $param) { $line_parts = CommentAnalyzer::splitDocLine($param); - $line_parts[0] = str_replace("\n", '', preg_replace('@^[ \t]*\*@m', '', $line_parts[0])); + $line_parts[0] = CommentAnalyzer::sanitizeDocblockType($line_parts[0]); $info->if_this_is = [ - 'type' => str_replace("\n", '', $line_parts[0]), + 'type' => $line_parts[0], 'line_number' => $comment->getStartLine() + substr_count( $comment->getText(), "\n", @@ -265,7 +269,7 @@ public static function parse( if (count($param_parts) === 2) { $taint_type = $param_parts[1]; - if (strpos($taint_type, 'exec_') === 0) { + if (str_starts_with($taint_type, 'exec_')) { $taint_type = substr($taint_type, 5); if ($taint_type === 'tainted') { @@ -395,7 +399,7 @@ public static function parse( throw new IncorrectDocblockException('Misplaced variable'); } - $line_parts[1] = preg_replace('/,$/', '', $line_parts[1], 1); + $line_parts[1] = (string) preg_replace('/,$/', '', $line_parts[1], 1); $info->globals[] = [ 'name' => $line_parts[1], @@ -420,7 +424,7 @@ public static function parse( } if (isset($parsed_docblock->tags['since'])) { - $since = trim(reset($parsed_docblock->tags['since'])); + $since = trim((string) reset($parsed_docblock->tags['since'])); // only for phpstub files or @since 8.0.0 PHP // since @since is commonly used with the project version, not the PHP version // https://docs.phpdoc.org/3.0/guide/references/phpdoc/tags/since.html @@ -455,6 +459,7 @@ public static function parse( if (isset($parsed_docblock->tags['throws'])) { foreach ($parsed_docblock->tags['throws'] as $offset => $throws_entry) { + /** @psalm-suppress PossiblyInvalidArrayAccess */ $throws_class = preg_split('/[\s]+/', $throws_entry)[0]; if (!$throws_class) { @@ -484,7 +489,7 @@ public static function parse( $templates = []; if (isset($parsed_docblock->combined_tags['template'])) { foreach ($parsed_docblock->combined_tags['template'] as $offset => $template_line) { - $template_type = preg_split('/[\s]+/', preg_replace('@^[ \t]*\*@m', '', $template_line)); + $template_type = preg_split('/[\s]+/', CommentAnalyzer::sanitizeDocblockType($template_line)); if ($template_type === false) { throw new AssertionError(preg_last_error_msg()); } @@ -609,7 +614,7 @@ public static function parse( */ private static function sanitizeAssertionLineParts(array $line_parts): array { - if (count($line_parts) < 2 || strpos($line_parts[1], '$') === false) { + if (count($line_parts) < 2 || !str_contains($line_parts[1], '$')) { throw new IncorrectDocblockException('Misplaced variable'); } @@ -619,7 +624,7 @@ private static function sanitizeAssertionLineParts(array $line_parts): array $param_name_parts = explode('->', $line_parts[1]); foreach ($param_name_parts as $i => $param_name_part) { - if (substr($param_name_part, -2) === '()') { + if (str_ends_with($param_name_part, '()')) { $param_name_parts[$i] = strtolower($param_name_part); } } @@ -638,7 +643,7 @@ private static function extractReturnType( array $return_specials, FunctionDocblockComment $info, CodeLocation $code_location, - string $cased_function_id + string $cased_function_id, ): void { foreach ($return_specials as $offset => $return_block) { $return_lines = explode("\n", $return_block); @@ -742,7 +747,7 @@ private static function extractAllParamNames(array $lines): array private static function checkUnexpectedTags( ParsedDocblock $parsed_docblock, FunctionDocblockComment $info, - PhpParser\Comment\Doc $comment + PhpParser\Comment\Doc $comment, ): void { if (isset($parsed_docblock->tags['psalm-import-type'])) { $info->unexpected_tags['psalm-import-type']['lines'] = self::tagOffsetsToLines( diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php index 313a3713af4..2646f783108 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeDocblockScanner.php @@ -1,5 +1,7 @@ template_types[$template_name]; $param_type_mapping[$token_body] = $template_name; } else { - $template_as_type = $param_storage->type - ? $param_storage->type - : Type::getMixed(); + $template_as_type = $param_storage->type ?: Type::getMixed(); $storage->template_types[$template_name] = [ $template_function_id => $template_as_type, @@ -517,9 +519,8 @@ private static function getConditionalSanitizedTypeTokens( if ($token_body === 'func_num_args()') { $template_name = 'TFunctionArgCount'; - $storage->template_types[$template_name] = [ - $template_function_id => Type::getInt(), + 'fn-' . strtolower($storage->cased_name ?? '') => Type::getInt(), ]; $function_template_types[$template_name] @@ -532,7 +533,7 @@ private static function getConditionalSanitizedTypeTokens( $template_name = 'TPhpMajorVersion'; $storage->template_types[$template_name] = [ - $template_function_id => Type::getInt(), + 'fn-' . strtolower($storage->cased_name ?? '') => Type::getInt(), ]; $function_template_types[$template_name] @@ -545,7 +546,7 @@ private static function getConditionalSanitizedTypeTokens( $template_name = 'TPhpVersionId'; $storage->template_types[$template_name] = [ - $template_function_id => Type::getInt(), + 'fn-' . strtolower($storage->cased_name ?? '') => Type::getInt(), ]; $function_template_types[$template_name] @@ -575,7 +576,7 @@ private static function getAssertionParts( array $class_template_types, array $function_template_types, array $type_aliases, - ?string $self_fqcln + ?string $self_fqcln, ): ?array { $is_negation = false; $is_loose_equality = false; @@ -710,7 +711,7 @@ private static function improveParamsFromDocblock( array $docblock_params, PhpParser\Node\FunctionLike $function, bool $fake_method, - ?string $fq_classlike_name + ?string $fq_classlike_name, ): void { $base = $classlike_storage ? $classlike_storage->name . '::' : ''; @@ -726,7 +727,7 @@ private static function improveParamsFromDocblock( $param_name = $docblock_param['name']; $docblock_param_variadic = false; - if (strpos($param_name, '...') === 0) { + if (str_starts_with($param_name, '...')) { $docblock_param_variadic = true; $param_name = substr($param_name, 3); } @@ -920,11 +921,6 @@ private static function improveParamsFromDocblock( static fn(FunctionLikeParameter $p): bool => !$p->has_docblock_type && (!$p->type || $p->type->hasArray()), ); - if ($params_without_docblock_type) { - /** @psalm-suppress DeprecatedProperty remove in Psalm 6 */ - $storage->unused_docblock_params = $unused_docblock_params; - } - $storage->has_undertyped_native_parameters = $params_without_docblock_type !== []; $storage->unused_docblock_parameters = $unused_docblock_params; } @@ -948,7 +944,7 @@ private static function handleReturn( array $type_aliases, ?ClassLikeStorage $classlike_storage, string $cased_function_id, - FileStorage $file_storage + FileStorage $file_storage, ): void { if (!$fake_method && $docblock_info->return_type_line_number @@ -1084,7 +1080,7 @@ private static function handleReturn( private static function handleTaintFlow( FunctionDocblockComment $docblock_info, - FunctionLikeStorage $storage + FunctionLikeStorage $storage, ): void { if ($docblock_info->flows) { foreach ($docblock_info->flows as $flow) { @@ -1097,7 +1093,7 @@ private static function handleTaintFlow( $path_type = $matches[1]; } - $flow = preg_replace($fancy_path_regex, '->', $flow); + $flow = (string) preg_replace($fancy_path_regex, '->', $flow); } $flow_parts = explode('->', $flow); @@ -1105,7 +1101,7 @@ private static function handleTaintFlow( if (isset($flow_parts[1]) && trim($flow_parts[1]) === 'return') { $source_param_string = trim($flow_parts[0]); - if ($source_param_string[0] === '(' && substr($source_param_string, -1) === ')') { + if ($source_param_string[0] === '(' && str_ends_with($source_param_string, ')')) { $source_params = preg_split('/, ?/', substr($source_param_string, 1, -1)); if ($source_params === false) { throw new AssertionError(preg_last_error_msg()); @@ -1123,7 +1119,7 @@ private static function handleTaintFlow( } } - if (isset($flow_parts[0]) && strpos(trim($flow_parts[0]), 'proxy') === 0) { + if (isset($flow_parts[0]) && str_starts_with(trim($flow_parts[0]), 'proxy')) { $proxy_call = trim(substr($flow_parts[0], strlen('proxy'))); [$fully_qualified_name, $source_param_string] = explode('(', $proxy_call, 2); @@ -1172,7 +1168,7 @@ private static function handleRemovedTaint( ?ClassLikeStorage $classlike_storage, string $cased_function_id, FileStorage $file_storage, - FileScanner $file_scanner + FileScanner $file_scanner, ): void { try { [$fixed_type_tokens, $function_template_types] = self::getConditionalSanitizedTypeTokens( @@ -1227,7 +1223,7 @@ private static function handleAssertions( array $class_template_types, array $function_template_types, array $type_aliases, - ?ClassLikeStorage $classlike_storage + ?ClassLikeStorage $classlike_storage, ): void { if ($docblock_info->assertions) { $storage->assertions = []; @@ -1260,7 +1256,7 @@ private static function handleAssertions( continue 2; } - if (strpos($assertion['param_name'], $param->name.'->') === 0) { + if (str_starts_with($assertion['param_name'], $param->name.'->')) { $storage->assertions[] = new Possibilities( substr_replace($assertion['param_name'], (string) $i, 0, strlen($param->name)), $assertion_type_parts, @@ -1270,7 +1266,7 @@ private static function handleAssertions( } $storage->assertions[] = new Possibilities( - (strpos($assertion['param_name'], '$') === false ? '$' : '') . $assertion['param_name'], + (!str_contains($assertion['param_name'], '$') ? '$' : '') . $assertion['param_name'], $assertion_type_parts, ); } @@ -1307,7 +1303,7 @@ private static function handleAssertions( continue 2; } - if (strpos($assertion['param_name'], $param->name.'->') === 0) { + if (str_starts_with($assertion['param_name'], $param->name.'->')) { $storage->if_true_assertions[] = new Possibilities( str_replace($param->name, (string) $i, $assertion['param_name']), $assertion_type_parts, @@ -1317,7 +1313,7 @@ private static function handleAssertions( } $storage->if_true_assertions[] = new Possibilities( - (strpos($assertion['param_name'], '$') === false ? '$' : '') . $assertion['param_name'], + (!str_contains($assertion['param_name'], '$') ? '$' : '') . $assertion['param_name'], $assertion_type_parts, ); } @@ -1354,7 +1350,7 @@ private static function handleAssertions( continue 2; } - if (strpos($assertion['param_name'], $param->name.'->') === 0) { + if (str_starts_with($assertion['param_name'], $param->name.'->')) { $storage->if_false_assertions[] = new Possibilities( str_replace($param->name, (string) $i, $assertion['param_name']), $assertion_type_parts, @@ -1364,7 +1360,7 @@ private static function handleAssertions( } $storage->if_false_assertions[] = new Possibilities( - (strpos($assertion['param_name'], '$') === false ? '$' : '') . $assertion['param_name'], + (!str_contains($assertion['param_name'], '$') ? '$' : '') . $assertion['param_name'], $assertion_type_parts, ); } @@ -1388,7 +1384,7 @@ private static function handleParamOut( PhpParser\Node\FunctionLike $stmt, FunctionLikeStorage $storage, Codebase $codebase, - FileStorage $file_storage + FileStorage $file_storage, ): void { $param_name = substr($docblock_param_out['name'], 1); @@ -1440,7 +1436,7 @@ private static function handleTemplates( array $type_aliases, FileScanner $file_scanner, PhpParser\Node\FunctionLike $stmt, - string $cased_function_id + string $cased_function_id, ): array { $storage->template_types = []; @@ -1509,7 +1505,7 @@ private static function handleUnexpectedTags( FunctionLikeStorage $storage, PhpParser\Node\FunctionLike $stmt, FileScanner $file_scanner, - string $cased_function_id + string $cased_function_id, ): void { foreach ($docblock_info->unexpected_tags as $tag => $details) { foreach ($details['lines'] as $line) { diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php index fa2d09f2a98..08e97ccda1a 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/FunctionLikeNodeScanner.php @@ -1,14 +1,16 @@ > - */ - private array $existing_function_template_types; - - private Aliases $aliases; - - /** - * @var array - */ - private array $type_aliases; + private readonly Config $config; public ?FunctionLikeStorage $storage = null; @@ -99,31 +82,26 @@ final class FunctionLikeNodeScanner * @param array $type_aliases */ public function __construct( - Codebase $codebase, - FileScanner $file_scanner, - FileStorage $file_storage, - Aliases $aliases, - array $type_aliases, - ?ClassLikeStorage $classlike_storage, - array $existing_function_template_types + private readonly Codebase $codebase, + private readonly FileScanner $file_scanner, + private readonly FileStorage $file_storage, + private readonly Aliases $aliases, + private readonly array $type_aliases, + private readonly ?ClassLikeStorage $classlike_storage, + private readonly array $existing_function_template_types, ) { - $this->codebase = $codebase; - $this->file_storage = $file_storage; - $this->file_scanner = $file_scanner; $this->file_path = $file_storage->file_path; - $this->aliases = $aliases; - $this->type_aliases = $type_aliases; $this->config = Config::getInstance(); - $this->classlike_storage = $classlike_storage; - $this->existing_function_template_types = $existing_function_template_types; } /** * @param bool $fake_method in the case of @method annotations we do something a little strange - * @return FunctionStorage|MethodStorage|false */ - public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = false) - { + public function start( + PhpParser\Node\FunctionLike $stmt, + bool $fake_method = false, + ?PhpParser\Comment\Doc $doc_comment = null, + ): FunctionStorage|MethodStorage|false { if ($stmt instanceof PhpParser\Node\Expr\Closure || $stmt instanceof PhpParser\Node\Expr\ArrowFunction ) { @@ -243,6 +221,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal if ($stmt instanceof PhpParser\Node\Stmt\Function_ || $stmt instanceof PhpParser\Node\Stmt\ClassMethod ) { + /** @psalm-suppress RedundantCondition See https://github.com/vimeo/psalm/issues/10296 */ if ($stmt instanceof PhpParser\Node\Stmt\ClassMethod && $storage instanceof MethodStorage && $classlike_storage @@ -267,7 +246,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $classlike_storage->properties[$property_name]->getter_method = strtolower($stmt->name->name); } - } elseif (strpos($stmt->name->name, 'assert') === 0 + } elseif (str_starts_with($stmt->name->name, 'assert') && $stmt->stmts ) { $var_assertions = []; @@ -299,7 +278,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal try { $negated_formula = Algebra::negateFormula($if_clauses); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $var_assertions = []; break; } @@ -325,7 +304,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $param_offset, $rule_part, ); - } elseif (strpos($var_id, '$this->') === 0) { + } elseif (str_starts_with($var_id, '$this->')) { $var_assertions[] = new Possibilities( $var_id, $rule_part, @@ -433,7 +412,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $storage->returns_by_ref = true; } - $doc_comment = $stmt->getDocComment(); + $doc_comment = $stmt->getDocComment() ?? $doc_comment; if ($classlike_storage && !$classlike_storage->is_trait) { @@ -640,7 +619,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $property_storage->location = $param_storage->location; $property_storage->stmt_location = new CodeLocation($this->file_scanner, $param); $property_storage->has_default = (bool)$param->default; - $param_type_readonly = (bool)($param->flags & PhpParser\Node\Stmt\Class_::MODIFIER_READONLY); + $param_type_readonly = (bool)($param->flags & PhpParser\Modifiers::READONLY); $property_storage->readonly = $param_type_readonly ?: $var_comment_readonly; $property_storage->allow_private_mutation = $var_comment_allow_private_mutation; $param_storage->promoted_property = true; @@ -648,18 +627,18 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal $property_id = $fq_classlike_name . '::$' . $param_storage->name; - switch ($param->flags & Class_::VISIBILITY_MODIFIER_MASK) { - case Class_::MODIFIER_PUBLIC: + switch ($param->flags & Modifiers::VISIBILITY_MASK) { + case Modifiers::PUBLIC: $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; $classlike_storage->inheritable_property_ids[$param_storage->name] = $property_id; break; - case Class_::MODIFIER_PROTECTED: + case Modifiers::PROTECTED: $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PROTECTED; $classlike_storage->inheritable_property_ids[$param_storage->name] = $property_id; break; - case Class_::MODIFIER_PRIVATE: + case Modifiers::PRIVATE: $property_storage->visibility = ClassLikeAnalyzer::VISIBILITY_PRIVATE; break; } @@ -733,7 +712,7 @@ public function start(PhpParser\Node\FunctionLike $stmt, bool $fake_method = fal private function inferPropertyTypeFromConstructor( PhpParser\Node\Stmt\ClassMethod $stmt, MethodStorage $storage, - ClassLikeStorage $classlike_storage + ClassLikeStorage $classlike_storage, ): void { if (!$stmt->stmts) { return; @@ -800,7 +779,7 @@ private function getTranslatedFunctionParam( PhpParser\Node\Param $param, PhpParser\Node\FunctionLike $stmt, bool $fake_method, - ?string $fq_classlike_name + ?string $fq_classlike_name, ): FunctionLikeParameter { $param_type = null; @@ -927,6 +906,7 @@ private function createStorageForFunctionLike( $storage->is_static = $stmt->isStatic(); $storage->final = $this->classlike_storage && $this->classlike_storage->final; $storage->final_from_docblock = $this->classlike_storage && $this->classlike_storage->final_from_docblock; + $storage->visibility = ClassLikeAnalyzer::VISIBILITY_PUBLIC; } elseif ($stmt instanceof PhpParser\Node\Stmt\Function_) { $cased_function_id = ($this->aliases->namespace ? $this->aliases->namespace . '\\' : '') . $stmt->name->name; @@ -1042,7 +1022,7 @@ private function createStorageForFunctionLike( $code_location, $cased_function_id, ); - } catch (IncorrectDocblockException|DocblockParseException $e) { + } catch (IncorrectDocblockException|DocblockParseException) { } if ($docblock_info) { if ($docblock_info->since_php_major_version && !$this->aliases->namespace) { @@ -1077,7 +1057,7 @@ private function createStorageForFunctionLike( if ($method_name_lc === strtolower($class_name) && !isset($classlike_storage->methods['__construct']) - && strpos($fq_classlike_name, '\\') === false + && !str_contains($fq_classlike_name, '\\') && $this->codebase->analysis_php_version_id <= 7_04_00 ) { $this->codebase->methods->setDeclaringMethodId( diff --git a/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php b/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php index 41c91d9ad39..786b59e72d3 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/TypeHintResolver.php @@ -1,5 +1,7 @@ @@ -90,20 +83,23 @@ final class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements Fi * @var array */ private array $bad_classes = []; - private EventDispatcher $eventDispatcher; + private readonly EventDispatcher $eventDispatcher; + + /** + * @var SplObjectStorage + */ + private readonly SplObjectStorage $closure_statements; public function __construct( - Codebase $codebase, - FileScanner $file_scanner, - FileStorage $file_storage + private readonly Codebase $codebase, + private readonly FileScanner $file_scanner, + private readonly FileStorage $file_storage, ) { - $this->codebase = $codebase; - $this->file_scanner = $file_scanner; $this->file_path = $file_scanner->file_path; $this->scan_deep = $file_scanner->will_analyze; - $this->file_storage = $file_storage; $this->aliases = $this->file_storage->aliases = new Aliases(); $this->eventDispatcher = $this->codebase->config->eventDispatcher; + $this->closure_statements = new SplObjectStorage(); } public function enterNode(PhpParser\Node $node): ?int @@ -154,12 +150,12 @@ public function enterNode(PhpParser\Node $node): ?int if ($classlike_node_scanner->start($node) === false) { $this->bad_classes[spl_object_id($node)] = true; - return PhpParser\NodeTraverser::DONT_TRAVERSE_CURRENT_AND_CHILDREN; + return self::DONT_TRAVERSE_CURRENT_AND_CHILDREN; } $this->classlike_node_scanners[] = $classlike_node_scanner; - $this->type_aliases = array_merge($this->type_aliases, $classlike_node_scanner->type_aliases); + $this->type_aliases = [...$this->type_aliases, ...$classlike_node_scanner->type_aliases]; } elseif ($node instanceof PhpParser\Node\Stmt\TryCatch) { foreach ($node->catches as $catch) { foreach ($catch->types as $catch_type) { @@ -171,13 +167,34 @@ public function enterNode(PhpParser\Node $node): ?int } } } - } elseif ($node instanceof PhpParser\Node\FunctionLike) { + } elseif ($node instanceof PhpParser\Node\FunctionLike + || $node instanceof PhpParser\Node\Stmt\Expression + && ($node->expr instanceof PhpParser\Node\Expr\ArrowFunction + || $node->expr instanceof PhpParser\Node\Expr\Closure) + || $node instanceof PhpParser\Node\Arg + && ($node->value instanceof PhpParser\Node\Expr\ArrowFunction + || $node->value instanceof PhpParser\Node\Expr\Closure) + ) { + $doc_comment = null; if ($node instanceof PhpParser\Node\Stmt\Function_ || $node instanceof PhpParser\Node\Stmt\ClassMethod ) { if ($this->skip_if_descendants) { return null; } + } elseif ($node instanceof PhpParser\Node\Stmt\Expression) { + $doc_comment = $node->getDocComment(); + /** @var PhpParser\Node\FunctionLike */ + $node = $node->expr; + $this->closure_statements->attach($node); + } elseif ($node instanceof PhpParser\Node\Arg) { + $doc_comment = $node->getDocComment(); + /** @var PhpParser\Node\FunctionLike */ + $node = $node->value; + $this->closure_statements->attach($node); + } elseif ($this->closure_statements->contains($node)) { + // This is a closure that was already processed at the statement level. + return null; } $classlike_storage = null; @@ -204,7 +221,7 @@ public function enterNode(PhpParser\Node $node): ?int $functionlike_types, ); - $functionlike_node_scanner->start($node); + $functionlike_node_scanner->start($node, false, $doc_comment); $this->functionlike_node_scanners[] = $functionlike_node_scanner; @@ -219,13 +236,11 @@ public function enterNode(PhpParser\Node $node): ?int $classlike_storage->class_implements['stringable'] = 'Stringable'; } - if (PHP_VERSION_ID >= 8_00_00) { - $this->codebase->scanner->queueClassLikeForScanning('Stringable'); - } + $this->codebase->scanner->queueClassLikeForScanning('Stringable'); } if (!$this->scan_deep) { - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + return self::DONT_TRAVERSE_CHILDREN; } } elseif ($node instanceof PhpParser\Node\Stmt\Global_) { $functionlike_node_scanner = end($this->functionlike_node_scanners); @@ -349,7 +364,7 @@ public function enterNode(PhpParser\Node $node): ?int $template_types, $this->type_aliases, ); - } catch (DocblockParseException $e) { + } catch (DocblockParseException) { // do nothing } @@ -560,7 +575,7 @@ public function leaveNode(PhpParser\Node $node) ) { $e = reset($functionlike_node_scanner->storage->docblock_issues); - $fqcn_parts = explode('\\', get_class($e)); + $fqcn_parts = explode('\\', $e::class); $issue_type = array_pop($fqcn_parts); $message = $e instanceof TaintedInput diff --git a/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php b/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php index aa14b34613a..c10bc26ead5 100644 --- a/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/ShortClosureVisitor.php @@ -1,5 +1,7 @@ */ - protected array $used_variables = []; + private array $used_variables = []; public function enterNode(PhpParser\Node $node): ?int { diff --git a/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php b/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php index 1eca6d3fd10..065fbf1210d 100644 --- a/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php +++ b/src/Psalm/Internal/PhpVisitor/SimpleNameResolver.php @@ -1,5 +1,7 @@ start_change || $attrs['startFilePos'] > $this->end_change ) { - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + return PhpParser\NodeVisitor::DONT_TRAVERSE_CHILDREN; } } @@ -144,7 +146,10 @@ public function enterNode(Node $node): ?int return null; } - private function addAlias(Stmt\UseUse $use, int $type, ?Name $prefix = null): void + /** + * @param Stmt\Use_::TYPE_* $type + */ + private function addAlias(Node\UseItem $use, int $type, ?Name $prefix = null): void { // Add prefix for group uses /** @var Name $name */ @@ -200,7 +205,7 @@ private function resolveType(?Node $node): ?Node * @param Stmt\Use_::TYPE_* $type One of Stmt\Use_::TYPE_* * @return Name Resolved name, or original name with attribute */ - protected function resolveName(Name $name, int $type): Name + private function resolveName(Name $name, int $type): Name { $resolvedName = $this->nameContext->getResolvedName($name, $type); if (null !== $resolvedName) { @@ -218,12 +223,12 @@ protected function resolveName(Name $name, int $type): Name return $name; } - protected function resolveClassName(Name $name): Name + private function resolveClassName(Name $name): Name { return $this->resolveName($name, Stmt\Use_::TYPE_NORMAL); } - protected function addNamespacedName(Stmt\Class_ $node): void + private function addNamespacedName(Stmt\Class_ $node): void { $node->setAttribute('namespacedName', Name::concat( $this->nameContext->getNamespace(), @@ -231,7 +236,7 @@ protected function addNamespacedName(Stmt\Class_ $node): void )); } - protected function resolveAttrGroups(Stmt\Class_ $node): void + private function resolveAttrGroups(Stmt\Class_ $node): void { foreach ($node->attrGroups as $attrGroup) { foreach ($attrGroup->attrs as $attr) { @@ -240,7 +245,7 @@ protected function resolveAttrGroups(Stmt\Class_ $node): void } } - protected function resolveTrait(Stmt\Trait_ $node): void + private function resolveTrait(Stmt\Trait_ $node): void { $resolvedName = Name::concat($this->nameContext->getNamespace(), (string) $node->name); diff --git a/src/Psalm/Internal/PhpVisitor/TraitFinder.php b/src/Psalm/Internal/PhpVisitor/TraitFinder.php index eefc452b274..e7640cce7df 100644 --- a/src/Psalm/Internal/PhpVisitor/TraitFinder.php +++ b/src/Psalm/Internal/PhpVisitor/TraitFinder.php @@ -1,5 +1,7 @@ */ private array $matching_trait_nodes = []; - private string $fq_trait_name; - - public function __construct(string $fq_trait_name) - { - $this->fq_trait_name = $fq_trait_name; + public function __construct( + private readonly string $fq_trait_name, + ) { } public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true): ?int @@ -53,7 +53,7 @@ public function enterNode(PhpParser\Node $node, bool &$traverseChildren = true): if ($node instanceof PhpParser\Node\Stmt\ClassLike || $node instanceof PhpParser\Node\FunctionLike ) { - return PhpParser\NodeTraverser::DONT_TRAVERSE_CHILDREN; + return PhpParser\NodeVisitor::DONT_TRAVERSE_CHILDREN; } return null; @@ -71,7 +71,7 @@ public function getNode(): ?PhpParser\Node\Stmt\Trait_ try { $reflection_trait = new ReflectionClass($this->fq_trait_name); - } catch (Throwable $t) { + } catch (Throwable) { return null; } diff --git a/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php b/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php index a42a9881dbf..5f9c6d7088c 100644 --- a/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php +++ b/src/Psalm/Internal/PhpVisitor/TypeMappingVisitor.php @@ -13,15 +13,10 @@ */ final class TypeMappingVisitor extends NodeVisitorAbstract { - private NodeDataProvider $fake_type_provider; - private NodeDataProvider $real_type_provider; - public function __construct( - NodeDataProvider $fake_type_provider, - NodeDataProvider $real_type_provider + private readonly NodeDataProvider $fake_type_provider, + private readonly NodeDataProvider $real_type_provider, ) { - $this->fake_type_provider = $fake_type_provider; - $this->real_type_provider = $real_type_provider; } /** diff --git a/src/Psalm/Internal/PhpVisitor/YieldTypeCollector.php b/src/Psalm/Internal/PhpVisitor/YieldTypeCollector.php index 881b28b3644..03912348153 100644 --- a/src/Psalm/Internal/PhpVisitor/YieldTypeCollector.php +++ b/src/Psalm/Internal/PhpVisitor/YieldTypeCollector.php @@ -1,12 +1,13 @@ */ private array $yield_types = []; - private NodeDataProvider $nodes; - - public function __construct(NodeDataProvider $nodes) - { - $this->nodes = $nodes; + public function __construct( + private readonly NodeDataProvider $nodes, + ) { } public function enterNode(Node $node): ?int @@ -43,7 +42,7 @@ public function enterNode(Node $node): ?int $generator_type = new TGenericObject( 'Generator', [ - $key_type ? $key_type : Type::getInt(), + $key_type ?: Type::getInt(), $value_type, Type::getMixed(), Type::getMixed(), @@ -63,7 +62,7 @@ public function enterNode(Node $node): ?int $this->yield_types []= Type::getMixed(); } elseif ($node instanceof FunctionLike) { - return NodeTraverser::DONT_TRAVERSE_CHILDREN; + return self::DONT_TRAVERSE_CHILDREN; } return null; diff --git a/src/Psalm/Internal/PluginManager/Command/DisableCommand.php b/src/Psalm/Internal/PluginManager/Command/DisableCommand.php index 7c1e6b9a27a..1678aea830a 100644 --- a/src/Psalm/Internal/PluginManager/Command/DisableCommand.php +++ b/src/Psalm/Internal/PluginManager/Command/DisableCommand.php @@ -1,5 +1,7 @@ plugin_list_factory = $plugin_list_factory; + public function __construct( + private readonly PluginListFactory $plugin_list_factory, + ) { parent::__construct(); } @@ -63,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $plugin_class = $plugin_list->resolvePluginClass($plugin_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $io->error('Unknown plugin class ' . $plugin_name); return 2; diff --git a/src/Psalm/Internal/PluginManager/Command/EnableCommand.php b/src/Psalm/Internal/PluginManager/Command/EnableCommand.php index 0a8df8d1dfe..d501c7a2acb 100644 --- a/src/Psalm/Internal/PluginManager/Command/EnableCommand.php +++ b/src/Psalm/Internal/PluginManager/Command/EnableCommand.php @@ -1,5 +1,7 @@ plugin_list_factory = $plugin_list_factory; + public function __construct( + private readonly PluginListFactory $plugin_list_factory, + ) { parent::__construct(); } @@ -63,7 +63,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int try { $plugin_class = $plugin_list->resolvePluginClass($plugin_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { $io->error('Unknown plugin class ' . $plugin_name); return 2; diff --git a/src/Psalm/Internal/PluginManager/Command/ShowCommand.php b/src/Psalm/Internal/PluginManager/Command/ShowCommand.php index ecc24712ce4..44515a554c1 100644 --- a/src/Psalm/Internal/PluginManager/Command/ShowCommand.php +++ b/src/Psalm/Internal/PluginManager/Command/ShowCommand.php @@ -1,5 +1,7 @@ plugin_list_factory = $plugin_list_factory; + public function __construct( + private readonly PluginListFactory $plugin_list_factory, + ) { parent::__construct(); } diff --git a/src/Psalm/Internal/PluginManager/ComposerLock.php b/src/Psalm/Internal/PluginManager/ComposerLock.php index 43bfdcfceaa..d806b09d81a 100644 --- a/src/Psalm/Internal/PluginManager/ComposerLock.php +++ b/src/Psalm/Internal/PluginManager/ComposerLock.php @@ -1,10 +1,13 @@ file_names = $file_names; + public function __construct( + private readonly array $file_names, + ) { } /** - * @param mixed $package * @psalm-assert-if-true array{ * name: string, * extra: array{psalm: array{pluginClass: string}} * } $package * @psalm-pure */ - public function isPlugin($package): bool + public function isPlugin(mixed $package): bool { return is_array($package) && isset($package['name'], $package['extra']['psalm']['pluginClass']) @@ -60,7 +59,10 @@ public function getPlugins(): array private function read(string $file_name): array { - $contents = json_decode(file_get_contents($file_name), true); + $file_contents = file_get_contents($file_name); + assert($file_contents !== false); + + $contents = json_decode($file_contents, true); if ($error = json_last_error()) { throw new RuntimeException(json_last_error_msg(), $error); diff --git a/src/Psalm/Internal/PluginManager/ConfigFile.php b/src/Psalm/Internal/PluginManager/ConfigFile.php index 4fdc21a8823..bb9883c2808 100644 --- a/src/Psalm/Internal/PluginManager/ConfigFile.php +++ b/src/Psalm/Internal/PluginManager/ConfigFile.php @@ -1,5 +1,7 @@ current_dir = $current_dir; - + public function __construct( + private readonly string $current_dir, + ?string $explicit_path, + ) { if ($explicit_path) { $this->path = $explicit_path; } else { @@ -111,6 +111,7 @@ private function readXml(): DOMDocument $doc = new DOMDocument(); $file_contents = file_get_contents($this->path); + assert($file_contents !== false); if (($tag_start = strpos($file_contents, '', $tag_start + 1); diff --git a/src/Psalm/Internal/PluginManager/PluginList.php b/src/Psalm/Internal/PluginManager/PluginList.php index 0eb325bd28c..b25536ffe27 100644 --- a/src/Psalm/Internal/PluginManager/PluginList.php +++ b/src/Psalm/Internal/PluginManager/PluginList.php @@ -1,5 +1,7 @@ [pluginClass => packageName] */ private ?array $all_plugins = null; /** @var ?array [pluginClass => ?packageName] */ private ?array $enabled_plugins = null; - public function __construct(?ConfigFile $config_file, ComposerLock $composer_lock) - { - $this->config_file = $config_file; - $this->composer_lock = $composer_lock; + public function __construct( + private readonly ?ConfigFile $config_file, + private readonly ComposerLock $composer_lock, + ) { } /** @@ -72,7 +70,7 @@ public function getAll(): array public function resolvePluginClass(string $class_or_package): string { - if (false === strpos($class_or_package, '/')) { + if (!str_contains($class_or_package, '/')) { return $class_or_package; // must be a class then } diff --git a/src/Psalm/Internal/PluginManager/PluginListFactory.php b/src/Psalm/Internal/PluginManager/PluginListFactory.php index 927d0f8c32e..32fb79f7c74 100644 --- a/src/Psalm/Internal/PluginManager/PluginListFactory.php +++ b/src/Psalm/Internal/PluginManager/PluginListFactory.php @@ -1,5 +1,7 @@ project_root = $project_root; - $this->psalm_root = $psalm_root; + public function __construct( + private readonly string $project_root, + private readonly string $psalm_root, + ) { } public function __invoke(string $current_dir, ?string $config_file_path = null): PluginList { try { $config_file = new ConfigFile($current_dir, $config_file_path); - } catch (RuntimeException $exception) { + } catch (RuntimeException) { $config_file = null; } $composer_lock = new ComposerLock($this->findLockFiles()); diff --git a/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php b/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php index bfb973faba0..b0a291f2b3f 100644 --- a/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php +++ b/src/Psalm/Internal/Provider/AddRemoveTaints/HtmlFunctionTainter.php @@ -1,5 +1,7 @@ modified_timestamps .= ' ' . filemtime($dependent_file_path); + $this->modified_timestamps .= ' ' . (int) filemtime($dependent_file_path); } $this->modified_timestamps .= $config->computeHash(); @@ -83,7 +83,7 @@ public function writeToCache(ClassLikeStorage $storage, string $file_path, strin public function getLatestFromCache( string $fq_classlike_name_lc, ?string $file_path, - ?string $file_contents + ?string $file_contents, ): ClassLikeStorage { $cached_value = $this->loadFromCache($fq_classlike_name_lc, $file_path); @@ -94,7 +94,7 @@ public function getLatestFromCache( $cache_hash = $this->getCacheHash($file_path, $file_contents); /** @psalm-suppress TypeDoesNotContainType */ - if (@get_class($cached_value) === '__PHP_Incomplete_Class' + if (@$cached_value::class === '__PHP_Incomplete_Class' || $cache_hash !== $cached_value->hash ) { $this->cache->deleteItem($this->getCacheLocationForClass($fq_classlike_name_lc, $file_path)); @@ -107,8 +107,8 @@ public function getLatestFromCache( private function getCacheHash(?string $_unused_file_path, ?string $file_contents): string { - $data = $file_contents ? $file_contents : $this->modified_timestamps; - return PHP_VERSION_ID >= 8_01_00 ? hash('xxh128', $data) : hash('md4', $data); + $data = $file_contents ?: $this->modified_timestamps; + return hash('xxh128', $data); } /** @@ -130,7 +130,7 @@ private function loadFromCache(string $fq_classlike_name_lc, ?string $file_path) private function getCacheLocationForClass( string $fq_classlike_name_lc, ?string $file_path, - bool $create_directory = false + bool $create_directory = false, ): string { $root_cache_directory = $this->cache->getCacheDirectory(); @@ -160,7 +160,7 @@ private function getCacheLocationForClass( $data = $file_path ? strtolower($file_path) . ' ' : ''; $data .= $fq_classlike_name_lc; - $file_path_sha = PHP_VERSION_ID >= 8_01_00 ? hash('xxh128', $data) : hash('md4', $data); + $file_path_sha = hash('xxh128', $data); return $parser_cache_directory . DIRECTORY_SEPARATOR diff --git a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php index 353bdb3f408..1be26f84398 100644 --- a/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php +++ b/src/Psalm/Internal/Provider/ClassLikeStorageProvider.php @@ -1,12 +1,13 @@ cache = $cache; } /** @@ -101,8 +99,8 @@ public function getNew(): array */ public function addMore(array $more): void { - self::$new_storage = array_merge(self::$new_storage, $more); - self::$storage = array_merge(self::$storage, $more); + self::$new_storage = [...self::$new_storage, ...$more]; + self::$storage = [...self::$storage, ...$more]; } public function makeNew(string $fq_classlike_name_lc): void diff --git a/src/Psalm/Internal/Provider/DynamicFunctionStorageProvider.php b/src/Psalm/Internal/Provider/DynamicFunctionStorageProvider.php index 059fb29ec09..9babb09b7c4 100644 --- a/src/Psalm/Internal/Provider/DynamicFunctionStorageProvider.php +++ b/src/Psalm/Internal/Provider/DynamicFunctionStorageProvider.php @@ -37,7 +37,7 @@ final class DynamicFunctionStorageProvider */ public function registerClass(string $class): void { - $callable = Closure::fromCallable([$class, 'getFunctionStorage']); + $callable = $class::getFunctionStorage(...); foreach ($class::getFunctionIds() as $function_id) { $this->registerClosure($function_id, $callable); @@ -62,7 +62,7 @@ public function getFunctionStorage( StatementsAnalyzer $statements_analyzer, string $function_id, Context $context, - CodeLocation $code_location + CodeLocation $code_location, ): ?FunctionStorage { if ($stmt->isFirstClassCallable()) { return null; diff --git a/src/Psalm/Internal/Provider/FakeFileProvider.php b/src/Psalm/Internal/Provider/FakeFileProvider.php index 8611bac1c3c..24b78fac33b 100644 --- a/src/Psalm/Internal/Provider/FakeFileProvider.php +++ b/src/Psalm/Internal/Provider/FakeFileProvider.php @@ -1,9 +1,11 @@ fake_files as $file_path => $_) { - if (strpos($file_path, $dir_path) === 0) { + if (str_starts_with($file_path, $dir_path)) { $file_paths[] = $file_path; } } diff --git a/src/Psalm/Internal/Provider/FileProvider.php b/src/Psalm/Internal/Provider/FileProvider.php index 948dec5d33b..91803a37061 100644 --- a/src/Psalm/Internal/Provider/FileProvider.php +++ b/src/Psalm/Internal/Provider/FileProvider.php @@ -1,5 +1,7 @@ hasChildren()) { $path = $current . DIRECTORY_SEPARATOR; } else { diff --git a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php index cb3029712f1..1721696a01f 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -1,5 +1,7 @@ config = $config; - $this->cache = new Cache($config); + $this->cache = new Cache($this->config); } public function hasConfigChanged(): bool @@ -227,7 +226,7 @@ public function setCachedIssues(array $issues): void /** * @return array>|false */ - public function getAnalyzedMethodCache() + public function getAnalyzedMethodCache(): array|false { /** @var null|array> $cache_item */ $cache_item = $this->getCacheItem(self::ANALYZED_METHODS_CACHE_NAME); @@ -246,7 +245,7 @@ public function setAnalyzedMethodCache(array $analyzed_methods): void /** * @return array|false */ - public function getFileMapCache() + public function getFileMapCache(): array|false { /** @var array|null $cache_item */ $cache_item = $this->getCacheItem(self::FILE_MAPS_CACHE_NAME); @@ -283,10 +282,8 @@ public function setTypeCoverage(array $mixed_counts): void $this->saveCacheItem(self::TYPE_COVERAGE_CACHE_NAME, $mixed_counts); } - /** - * @return string|false - */ - public function getConfigHashCache() + /** @return string|false */ + public function getConfigHashCache(): string|bool { $cache_directory = $this->config->getCacheDirectory(); diff --git a/src/Psalm/Internal/Provider/FileReferenceProvider.php b/src/Psalm/Internal/Provider/FileReferenceProvider.php index 87a77167184..22d6a3fe48b 100644 --- a/src/Psalm/Internal/Provider/FileReferenceProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceProvider.php @@ -1,5 +1,7 @@ file_provider = $file_provider; - $this->cache = $cache; + public function __construct( + private readonly FileProvider $file_provider, + public ?FileReferenceCacheProvider $cache = null, + ) { } /** @@ -231,7 +230,7 @@ public function addClassLikeFiles(array $map): void public function addFileReferenceToClassMember( string $source_file, string $referenced_member_id, - bool $inside_return + bool $inside_return, ): void { self::$file_references_to_class_members[$referenced_member_id][$source_file] = true; @@ -384,7 +383,7 @@ private function calculateFilesReferencingFile(Codebase $codebase, string $file) try { $referenced_files[] = $codebase->scanner->getClassLikeFilePath($fq_class_name_lc); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { if (isset(self::$classlike_files[$fq_class_name_lc])) { $referenced_files[] = self::$classlike_files[$fq_class_name_lc]; } @@ -736,7 +735,7 @@ public function addMethodReferenceToClass(string $calling_function_id, string $f public function addMethodReferenceToClassMember( string $calling_function_id, string $referenced_member_id, - bool $inside_return + bool $inside_return, ): void { if (!isset(self::$method_references_to_class_members[$referenced_member_id])) { self::$method_references_to_class_members[$referenced_member_id] = [$calling_function_id => true]; @@ -755,7 +754,7 @@ public function addMethodReferenceToClassMember( public function addMethodDependencyToClassMember( string $calling_function_id, - string $referenced_member_id + string $referenced_member_id, ): void { if (!isset(self::$method_dependencies[$referenced_member_id])) { self::$method_dependencies[$referenced_member_id] = [$calling_function_id => true]; @@ -775,7 +774,7 @@ public function addMethodReferenceToClassProperty(string $calling_function_id, s public function addMethodReferenceToMissingClassMember( string $calling_function_id, - string $referenced_member_id + string $referenced_member_id, ): void { if (!isset(self::$method_references_to_missing_class_members[$referenced_member_id])) { self::$method_references_to_missing_class_members[$referenced_member_id] = [$calling_function_id => true]; @@ -795,7 +794,7 @@ public function addCallingLocationForClassMethod(CodeLocation $code_location, st public function addCallingLocationForClassProperty( CodeLocation $code_location, - string $referenced_property_id + string $referenced_property_id, ): void { if (!isset(self::$class_property_locations[$referenced_property_id])) { self::$class_property_locations[$referenced_property_id] = [$code_location]; @@ -1231,7 +1230,7 @@ public function getTypeCoverage(): array */ public function setTypeCoverage(array $mixed_counts): void { - self::$mixed_counts = array_merge(self::$mixed_counts, $mixed_counts); + self::$mixed_counts = [...self::$mixed_counts, ...$mixed_counts]; } /** diff --git a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php index 874feacb339..a8b6f83a539 100644 --- a/src/Psalm/Internal/Provider/FileStorageCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileStorageCacheProvider.php @@ -1,5 +1,7 @@ modified_timestamps .= ' ' . filemtime($dependent_file_path); + $this->modified_timestamps .= ' ' . (int) filemtime($dependent_file_path); } $this->modified_timestamps .= $config->computeHash(); @@ -90,7 +90,7 @@ public function getLatestFromCache(string $file_path, string $file_contents): ?F $cache_hash = $this->getCacheHash($file_path, $file_contents); /** @psalm-suppress TypeDoesNotContainType */ - if (@get_class($cached_value) === '__PHP_Incomplete_Class' + if (@$cached_value::class === '__PHP_Incomplete_Class' || $cache_hash !== $cached_value->hash ) { $this->removeCacheForFile($file_path); @@ -111,8 +111,8 @@ private function getCacheHash(string $_unused_file_path, string $file_contents): // do not concatenate, as $file_contents can be big and performance will be bad // the timestamp is only needed if we don't have file contents // as same contents should give same results, independent of when file was modified - $data = $file_contents ? $file_contents : $this->modified_timestamps; - return PHP_VERSION_ID >= 8_01_00 ? hash('xxh128', $data) : hash('md4', $data); + $data = $file_contents ?: $this->modified_timestamps; + return hash('xxh128', $data); } /** @@ -156,11 +156,7 @@ private function getCacheLocationForPath(string $file_path, bool $create_directo } } - if (PHP_VERSION_ID >= 8_01_00) { - $hash = hash('xxh128', $file_path); - } else { - $hash = hash('md4', $file_path); - } + $hash = hash('xxh128', $file_path); return $parser_cache_directory . DIRECTORY_SEPARATOR diff --git a/src/Psalm/Internal/Provider/FileStorageProvider.php b/src/Psalm/Internal/Provider/FileStorageProvider.php index 91bbbbe8c52..747a11e6d60 100644 --- a/src/Psalm/Internal/Provider/FileStorageProvider.php +++ b/src/Psalm/Internal/Provider/FileStorageProvider.php @@ -1,11 +1,12 @@ cache = $cache; } public function get(string $file_path): FileStorage @@ -101,8 +99,8 @@ public function getNew(): array */ public function addMore(array $more): void { - self::$new_storage = array_merge(self::$new_storage, $more); - self::$storage = array_merge(self::$storage, $more); + self::$new_storage = [...self::$new_storage, ...$more]; + self::$storage = [...self::$storage, ...$more]; } public function create(string $file_path): FileStorage diff --git a/src/Psalm/Internal/Provider/FunctionExistenceProvider.php b/src/Psalm/Internal/Provider/FunctionExistenceProvider.php index 3f5c96f5f5c..7e84726d4e5 100644 --- a/src/Psalm/Internal/Provider/FunctionExistenceProvider.php +++ b/src/Psalm/Internal/Provider/FunctionExistenceProvider.php @@ -1,5 +1,7 @@ registerClosure($function_id, $callable); @@ -58,7 +60,7 @@ public function has(string $function_id): bool public function doesFunctionExist( StatementsSource $statements_source, - string $function_id + string $function_id, ): ?bool { foreach (self::$handlers[strtolower($function_id)] ?? [] as $function_handler) { $event = new FunctionExistenceProviderEvent( diff --git a/src/Psalm/Internal/Provider/FunctionParamsProvider.php b/src/Psalm/Internal/Provider/FunctionParamsProvider.php index b1ca3aaafd9..9a627bdb3c7 100644 --- a/src/Psalm/Internal/Provider/FunctionParamsProvider.php +++ b/src/Psalm/Internal/Provider/FunctionParamsProvider.php @@ -1,5 +1,7 @@ registerClosure($function_id, $callable); @@ -70,7 +72,7 @@ public function getFunctionParams( string $function_id, array $call_args, ?Context $context = null, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): ?array { foreach (self::$handlers[strtolower($function_id)] ?? [] as $class_handler) { $event = new FunctionParamsProviderEvent( diff --git a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php index 546b7d38a02..665be17edda 100644 --- a/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/FunctionReturnTypeProvider.php @@ -1,5 +1,7 @@ registerClosure($function_id, $callable); @@ -147,7 +149,7 @@ public function getReturnType( string $function_id, PhpParser\Node\Expr\FuncCall $stmt, Context $context, - CodeLocation $code_location + CodeLocation $code_location, ): ?Union { foreach (self::$handlers[strtolower($function_id)] ?? [] as $function_handler) { $event = new FunctionReturnTypeProviderEvent( diff --git a/src/Psalm/Internal/Provider/MethodExistenceProvider.php b/src/Psalm/Internal/Provider/MethodExistenceProvider.php index 7f85cf9e6ed..af345f77e3e 100644 --- a/src/Psalm/Internal/Provider/MethodExistenceProvider.php +++ b/src/Psalm/Internal/Provider/MethodExistenceProvider.php @@ -1,5 +1,7 @@ registerClosure($fq_classlike_name, $callable); @@ -57,7 +59,7 @@ public function doesMethodExist( string $fq_classlike_name, string $method_name_lowercase, ?StatementsSource $source = null, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): ?bool { foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $method_handler) { $event = new MethodExistenceProviderEvent( diff --git a/src/Psalm/Internal/Provider/MethodParamsProvider.php b/src/Psalm/Internal/Provider/MethodParamsProvider.php index 7074627cd05..8599c4b2916 100644 --- a/src/Psalm/Internal/Provider/MethodParamsProvider.php +++ b/src/Psalm/Internal/Provider/MethodParamsProvider.php @@ -1,5 +1,7 @@ registerClosure($fq_classlike_name, $callable); @@ -73,7 +75,7 @@ public function getMethodParams( ?array $call_args = null, ?StatementsSource $statements_source = null, ?Context $context = null, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): ?array { foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $class_handler) { $event = new MethodParamsProviderEvent( diff --git a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php index a892575be77..1e57683a2ea 100644 --- a/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/MethodReturnTypeProvider.php @@ -1,5 +1,7 @@ registerClosure($fq_classlike_name, $callable); @@ -71,19 +73,18 @@ public function has(string $fq_classlike_name): bool } /** - * @param PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $stmt * @param non-empty-list|null $template_type_parameters */ public function getReturnType( StatementsSource $statements_source, string $fq_classlike_name, string $method_name, - $stmt, + PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $stmt, Context $context, CodeLocation $code_location, ?array $template_type_parameters = null, ?string $called_fq_classlike_name = null, - ?string $called_method_name = null + ?string $called_method_name = null, ): ?Union { foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $class_handler) { $event = new MethodReturnTypeProviderEvent( diff --git a/src/Psalm/Internal/Provider/MethodVisibilityProvider.php b/src/Psalm/Internal/Provider/MethodVisibilityProvider.php index b4309d232df..a9d88825641 100644 --- a/src/Psalm/Internal/Provider/MethodVisibilityProvider.php +++ b/src/Psalm/Internal/Provider/MethodVisibilityProvider.php @@ -1,5 +1,7 @@ registerClosure($fq_classlike_name, $callable); @@ -63,7 +65,7 @@ public function isMethodVisible( string $fq_classlike_name, string $method_name, Context $context, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): ?bool { foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $method_handler) { $event = new MethodVisibilityProviderEvent( diff --git a/src/Psalm/Internal/Provider/NodeDataProvider.php b/src/Psalm/Internal/Provider/NodeDataProvider.php index bf384767b32..efed6126b11 100644 --- a/src/Psalm/Internal/Provider/NodeDataProvider.php +++ b/src/Psalm/Internal/Provider/NodeDataProvider.php @@ -1,5 +1,7 @@ cache = new Cache($config); - $this->use_file_cache = $use_file_cache; } /** @@ -71,7 +72,7 @@ public function __construct(Config $config, bool $use_file_cache = true) public function loadStatementsFromCache( string $file_path, int $file_modified_time, - string $file_content_hash + string $file_content_hash, ): ?array { if (!$this->use_file_cache) { return null; @@ -199,7 +200,7 @@ private function getExistingFileContentHashes(): array } /** @psalm-suppress MixedAssignment */ - $hashes_decoded = json_decode($hashes_encoded, true); + $hashes_decoded = json_decode($hashes_encoded, true, 512, JSON_THROW_ON_ERROR); if (!is_array($hashes_decoded)) { throw new UnexpectedValueException( @@ -221,7 +222,7 @@ public function saveStatementsToCache( string $file_path, string $file_content_hash, array $stmts, - bool $touch_only + bool $touch_only, ): void { $cache_location = $this->getCacheLocationForPath($file_path, self::PARSER_CACHE_DIRECTORY, !$touch_only); @@ -309,6 +310,7 @@ public function deleteOldParserCaches(float $time_before): int if (is_dir($cache_directory)) { $directory_files = scandir($cache_directory, SCANDIR_SORT_NONE); + assert($directory_files !== false); foreach ($directory_files as $directory_file) { $full_path = $cache_directory . DIRECTORY_SEPARATOR . $directory_file; @@ -329,11 +331,7 @@ public function deleteOldParserCaches(float $time_before): int private function getParserCacheKey(string $file_path): string { - if (PHP_VERSION_ID >= 8_01_00) { - $hash = hash('xxh128', $file_path); - } else { - $hash = hash('md4', $file_path); - } + $hash = hash('xxh128', $file_path); return $hash . ($this->cache->use_igbinary ? '-igbinary' : '') . '-r'; } @@ -342,7 +340,7 @@ private function getParserCacheKey(string $file_path): string private function getCacheLocationForPath( string $file_path, string $subdirectory, - bool $create_directory = false + bool $create_directory = false, ): string { $root_cache_directory = $this->cache->getCacheDirectory(); diff --git a/src/Psalm/Internal/Provider/ProjectCacheProvider.php b/src/Psalm/Internal/Provider/ProjectCacheProvider.php index 4948acc5f24..6200430f8dd 100644 --- a/src/Psalm/Internal/Provider/ProjectCacheProvider.php +++ b/src/Psalm/Internal/Provider/ProjectCacheProvider.php @@ -1,5 +1,7 @@ composer_lock_location = $composer_lock_location; + public function __construct( + private readonly string $composer_lock_location, + ) { } public function canDiffFiles(): bool @@ -67,7 +66,7 @@ public function getLastRun(string $psalm_version): int if (file_exists($run_cache_location) && Providers::safeFileGetContents($run_cache_location) === $psalm_version) { - $this->last_run = filemtime($run_cache_location); + $this->last_run = (int) filemtime($run_cache_location); } else { $this->last_run = 0; } @@ -84,11 +83,7 @@ public function hasLockfileChanged(): bool return true; } - if (PHP_VERSION_ID >= 8_01_00) { - $hash = hash('xxh128', $lockfile_contents); - } else { - $hash = hash('md4', $lockfile_contents); - } + $hash = hash('xxh128', $lockfile_contents); } else { $hash = ''; } @@ -117,7 +112,7 @@ public function updateComposerLockHash(): void file_put_contents($lock_hash_location, $this->composer_lock_hash); } - protected function getComposerLockHash(): string + private function getComposerLockHash(): string { if ($this->composer_lock_hash === null) { $cache_directory = Config::getInstance()->getCacheDirectory(); diff --git a/src/Psalm/Internal/Provider/PropertyExistenceProvider.php b/src/Psalm/Internal/Provider/PropertyExistenceProvider.php index 101bf1c2fe7..9f6e7b1a5e9 100644 --- a/src/Psalm/Internal/Provider/PropertyExistenceProvider.php +++ b/src/Psalm/Internal/Provider/PropertyExistenceProvider.php @@ -1,5 +1,7 @@ registerClosure($fq_classlike_name, $callable); @@ -64,7 +66,7 @@ public function doesPropertyExist( bool $read_mode, ?StatementsSource $source = null, ?Context $context = null, - ?CodeLocation $code_location = null + ?CodeLocation $code_location = null, ): ?bool { foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { $event = new PropertyExistenceProviderEvent( diff --git a/src/Psalm/Internal/Provider/PropertyTypeProvider.php b/src/Psalm/Internal/Provider/PropertyTypeProvider.php index 0089621023e..19687569af1 100644 --- a/src/Psalm/Internal/Provider/PropertyTypeProvider.php +++ b/src/Psalm/Internal/Provider/PropertyTypeProvider.php @@ -1,5 +1,7 @@ registerClosure($fq_classlike_name, $callable); @@ -65,7 +67,7 @@ public function getPropertyType( string $property_name, bool $read_mode, ?StatementsSource $source = null, - ?Context $context = null + ?Context $context = null, ): ?Union { if ($source) { diff --git a/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php b/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php index ea0f450a6a7..a9176a4fec2 100644 --- a/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php +++ b/src/Psalm/Internal/Provider/PropertyVisibilityProvider.php @@ -1,5 +1,7 @@ registerClosure($fq_classlike_name, $callable); @@ -60,7 +62,7 @@ public function isPropertyVisible( string $property_name, bool $read_mode, Context $context, - CodeLocation $code_location + CodeLocation $code_location, ): ?bool { foreach (self::$handlers[strtolower($fq_classlike_name)] ?? [] as $property_handler) { $event = new PropertyVisibilityProviderEvent( diff --git a/src/Psalm/Internal/Provider/Providers.php b/src/Psalm/Internal/Provider/Providers.php index 9e74bc67ef6..e6d5707dfd7 100644 --- a/src/Psalm/Internal/Provider/Providers.php +++ b/src/Psalm/Internal/Provider/Providers.php @@ -1,5 +1,7 @@ file_provider = $file_provider; - $this->parser_cache_provider = $parser_cache_provider; - $this->project_cache_provider = $project_cache_provider; - $this->file_storage_provider = new FileStorageProvider($file_storage_cache_provider); $this->classlike_storage_provider = new ClassLikeStorageProvider($classlike_storage_cache_provider); $this->statements_provider = new StatementsProvider( diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php index 22377c272e0..4f731d1ea0a 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayColumnReturnTypeProvider.php @@ -1,5 +1,7 @@ isSingle()) { if ($row_type->hasArray()) { diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php index 57de9047e55..9464a67b281 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayCombineReturnTypeProvider.php @@ -1,5 +1,7 @@ node_data->getType($call_args[1]->value) : null; $third_arg_type = isset($call_args[2]) ? $statements_source->node_data->getType($call_args[2]->value) : null; - $value_type_from_third_arg = $third_arg_type ? $third_arg_type : Type::getMixed(); + $value_type_from_third_arg = $third_arg_type ?: Type::getMixed(); if ($first_arg_type && $second_arg_type && $third_arg_type && $first_arg_type->isSingleIntLiteral() diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php index d5d32ac58c8..b5096323ecb 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php @@ -1,5 +1,7 @@ vars_in_scope[$callable_extended_var_id] ?? null; // @todo for array callables @@ -187,9 +189,9 @@ static function ($keyed_type) use ($statements_source, $context) { if ($function_call_arg->value instanceof PhpParser\Node\Scalar\String_ || $function_call_arg->value instanceof PhpParser\Node\Expr\Array_ || $function_call_arg->value instanceof PhpParser\Node\Expr\BinaryOp\Concat - || $mapping_function_ids !== array() + || $mapping_function_ids !== [] ) { - if ($mapping_function_ids === array()) { + if ($mapping_function_ids === []) { $mapping_function_ids = CallAnalyzer::getFunctionIdsFromCallableArg( $statements_source, $function_call_arg->value, @@ -291,7 +293,7 @@ static function ($keyed_type) use ($statements_source, $context) { $statements_source, $codebase, ); - } catch (ComplicatedExpressionException $e) { + } catch (ComplicatedExpressionException) { $filter_clauses = []; } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php index 3d30faa936d..795615b983f 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMapReturnTypeProvider.php @@ -1,5 +1,7 @@ getKeyedArray(); - } + $array_arg_type = ArrayType::infer($array_arg_atomic_type); } } @@ -164,6 +164,7 @@ static function (array $sub) use ($null) { if ($function_call_type->hasCallableType()) { $closure_types = $function_call_type->getClosureTypes() ?: $function_call_type->getCallableTypes(); $closure_atomic_type = reset($closure_types); + assert($closure_atomic_type !== false); $closure_return_type = $closure_atomic_type->return_type ?: Type::getMixed(); @@ -295,7 +296,7 @@ private static function executeFakeCall( StatementsAnalyzer $statements_analyzer, PhpParser\Node\Expr $fake_call, Context $context, - ?array &$assertions = null + ?array &$assertions = null, ): ?Union { $old_data_provider = $statements_analyzer->node_data; @@ -381,7 +382,7 @@ public static function getReturnTypeFromMappingIds( PhpParser\Node\Arg $function_call_arg, array $array_args, ?array &$assertions = null, - ?int $fake_var_discriminator = null + ?int $fake_var_discriminator = null, ): Union { $mapping_return_type = null; @@ -416,7 +417,7 @@ public static function getReturnTypeFromMappingIds( ); } - if (strpos($mapping_function_id_part, '::') !== false) { + if (str_contains($mapping_function_id_part, '::')) { $is_instance = false; if ($mapping_function_id_part[0] === '$') { @@ -526,7 +527,7 @@ public static function getReturnTypeFromMappingIds( public static function cleanContext(Context $context, int $fake_var_discriminator): void { foreach ($context->vars_in_scope as $var_in_scope => $_) { - if (strpos($var_in_scope, "__fake_{$fake_var_discriminator}_") !== false) { + if (str_contains($var_in_scope, "__fake_{$fake_var_discriminator}_")) { unset($context->vars_in_scope[$var_in_scope]); } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php index b93ff08826e..4efeda3d5f2 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayMergeReturnTypeProvider.php @@ -1,5 +1,7 @@ getAtomicTypes() as $type_part) { - if ($type_part instanceof TList) { - $type_part = $type_part->getKeyedArray(); - } $unpacking_indefinite_number_of_args = false; $unpacking_possibly_empty = false; if ($call_arg->unpack) { diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php index df1c3a0518a..42e146c36cc 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPointerAdjustmentReturnTypeProvider.php @@ -1,5 +1,7 @@ getKeyedArray(); - } + if ($atomic_type instanceof TArray) { $value_type = $atomic_type->type_params[1]; diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php index a167a8ea2b3..1769af6c536 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayPopReturnTypeProvider.php @@ -1,5 +1,7 @@ getKeyedArray(); - } + if ($array_arg_atomic_type instanceof TKeyedArray) { $array_arg_atomic_type = $array_arg_atomic_type->getGenericArrayType(); @@ -220,7 +218,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev $part_match_found = true; } } elseif ($mapping_function_id_part) { - if (strpos($mapping_function_id_part, '::') !== false) { + if (str_contains($mapping_function_id_part, '::')) { if ($mapping_function_id_part[0] === '$') { $mapping_function_id_part = substr($mapping_function_id_part, 1); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php index 8d20cd11b57..6e24a9134e4 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayReverseReturnTypeProvider.php @@ -1,5 +1,7 @@ getKeyedArray(); - } + if ($atomic_type instanceof TKeyedArray) { $atomic_type = $atomic_type->getGenericArrayType(); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php index 93bcd4c271e..5602839995a 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArraySpliceReturnTypeProvider.php @@ -1,5 +1,7 @@ INPUT_GET, - '$_POST' => INPUT_POST, + $possible_types = [ + '$_GET' => INPUT_GET, + '$_POST' => INPUT_POST, '$_COOKIE' => INPUT_COOKIE, '$_SERVER' => INPUT_SERVER, - '$_ENV' => INPUT_ENV, - ); + '$_ENV' => INPUT_ENV, + ]; $first_arg_type_type = $first_arg_type->getSingleIntLiteral(); $global_name = array_search($first_arg_type_type->value, $possible_types); @@ -209,7 +211,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } if (FilterUtils::hasFlag($flags_int_used, FILTER_REQUIRE_ARRAY) - && in_array($first_arg_type_type->value, array(INPUT_COOKIE, INPUT_SERVER, INPUT_ENV), true)) { + && in_array($first_arg_type_type->value, [INPUT_COOKIE, INPUT_SERVER, INPUT_ENV], true)) { // these globals can never be an array return $fails_or_not_set_type; } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterUtils.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterUtils.php index 5c32126de0e..f4c602d61e1 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterUtils.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterUtils.php @@ -1,5 +1,7 @@ node_data->getType($filter_arg->value); if (!$filter_arg_type) { return null; @@ -156,8 +156,8 @@ public static function getOptionsArgValueOrError( Codebase $codebase, CodeLocation $code_location, string $function_id, - int $filter_int_used - ) { + int $filter_int_used, + ): array|Union|null { $options_arg_type = $statements_analyzer->node_data->getType($options_arg->value); if (!$options_arg_type) { return null; @@ -234,8 +234,7 @@ public static function getOptionsArgValueOrError( // silently ignored by the function, but this usually indicates a bug IssueBuffer::maybeAdd( new InvalidArgument( - 'The "options" key in ' . $function_id - . ' must be a an array', + 'The "options" key in ' . $function_id . ' must be an array', $code_location, $function_id, ), @@ -341,7 +340,7 @@ public static function missingFilterCallbackCallable( string $function_id, CodeLocation $code_location, StatementsAnalyzer $statements_analyzer, - Codebase $codebase + Codebase $codebase, ): Union { IssueBuffer::maybeAdd( new InvalidArgument( @@ -399,7 +398,7 @@ public static function checkRedundantFlags( Union $fails_type, StatementsAnalyzer $statements_analyzer, CodeLocation $code_location, - Codebase $codebase + Codebase $codebase, ): ?Union { $all_filters = self::getFilters($codebase); $flags_int_used_rest = $flags_int_used; @@ -502,7 +501,7 @@ public static function getOptions( StatementsAnalyzer $statements_analyzer, CodeLocation $code_location, Codebase $codebase, - string $function_id + string $function_id, ): array { $default = null; $min_range = null; @@ -607,16 +606,12 @@ public static function getOptions( return [$default, $min_range, $max_range, $has_range, $regexp]; } - /** - * @param float|int|null $min_range - * @param float|int|null $max_range - */ protected static function isRangeValid( - $min_range, - $max_range, + float|int|null $min_range, + float|int|null $max_range, StatementsAnalyzer $statements_analyzer, CodeLocation $code_location, - string $function_id + string $function_id, ): bool { if ($min_range !== null && $max_range !== null && $min_range > $max_range) { IssueBuffer::maybeAdd( @@ -639,8 +634,6 @@ protected static function isRangeValid( * * @psalm-suppress ComplexMethod * @param Union|null $not_set_type null if undefined filtered variable will return $fails_type - * @param float|int|null $min_range - * @param float|int|null $max_range * @param non-falsy-string|true|null $regexp */ public static function getReturnType( @@ -654,10 +647,10 @@ public static function getReturnType( Codebase $codebase, string $function_id, bool $has_range, - $min_range, - $max_range, - $regexp, - bool $in_array_recursion = false + float|int|null $min_range, + float|int|null $max_range, + string|bool|null $regexp, + bool $in_array_recursion = false, ): Union { // if we are inside a recursion of e.g. array // it will never fail or change the type, so we can immediately return @@ -673,10 +666,6 @@ public static function getReturnType( && !self::hasFlag($flags_int_used, FILTER_REQUIRE_SCALAR) ) { foreach ($input_type->getAtomicTypes() as $key => $atomic_type) { - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } - if ($atomic_type instanceof TKeyedArray) { $input_type = $input_type->getBuilder(); $input_type->removeType($key); @@ -1481,7 +1470,7 @@ private static function addReturnTaint( StatementsAnalyzer $statements_analyzer, CodeLocation $code_location, Union $return_type, - string $function_id + string $function_id, ): Union { if ($statements_analyzer->data_flow_graph && !in_array('TaintedInput', $statements_analyzer->getSuppressedIssues()) diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php index 76f6caa6280..f3800f26713 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/FilterVarReturnTypeProvider.php @@ -1,5 +1,7 @@ enum_cases[$object_type->case_name]; $case_value = $enum_case_storage->getValue($statements_source->getCodebase()->classlikes); - if (is_int($case_value)) { - $properties['value'] = new Union([new Atomic\TLiteralInt($case_value)]); - } elseif (is_string($case_value)) { - $properties['value'] = new Union([Type::getAtomicStringFromLiteral($case_value)]); + + if ($case_value !== null) { + $properties['value'] = new Union([$case_value]); } + return new TKeyedArray($properties); } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php index f99d9bed7ef..ead0b9651d4 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/HexdecReturnTypeProvider.php @@ -1,5 +1,7 @@ isSingle() && $key_type->hasTemplate()) { $template_types = $key_type->getTemplateTypes(); $template_type = array_shift($template_types); + assert($template_type !== null); if ($template_type->as->hasMixed()) { $template_type = $template_type->replaceAs(Type::getArrayKey()); $key_type = new Union([$template_type]); diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php index db7000bc721..cd9f841bcb1 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MinMaxReturnTypeProvider.php @@ -19,7 +19,6 @@ use function array_filter; use function assert; use function count; -use function get_class; use function in_array; use function max; use function min; @@ -89,7 +88,7 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev } elseif ($atomic_type instanceof TIntRange) { $min_bounds[] = $atomic_type->min_bound; $max_bounds[] = $atomic_type->max_bound; - } elseif (get_class($atomic_type) === TInt::class) { + } elseif ($atomic_type::class === TInt::class) { $min_bounds[] = null; $max_bounds[] = null; } else { diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php index a65b838b1a5..5552f4bacd9 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/MktimeReturnTypeProvider.php @@ -1,5 +1,7 @@ |false - return new Union([ - new TArray([ - Type::getString(), - new Union([ - new TScalar(), - new TNull(), - ]), + return match ($fetch_mode) { + 2 => new Union([ + new TArray([ + Type::getString(), + new Union([ + new TScalar(), + new TNull(), ]), - new TFalse(), - ]); - - case 4: // PDO::FETCH_BOTH - array|false - return new Union([ - new TArray([ - Type::getArrayKey(), - new Union([ - new TScalar(), - new TNull(), - ]), + ]), + new TFalse(), + ]), + 4 => new Union([ + new TArray([ + Type::getArrayKey(), + new Union([ + new TScalar(), + new TNull(), ]), - new TFalse(), - ]); - - case 6: // PDO::FETCH_BOUND - bool - return Type::getBool(); - - case 7: // PDO::FETCH_COLUMN - scalar|null|false - return new Union([ - new TScalar(), - new TNull(), - new TFalse(), - ]); - - case 8: // PDO::FETCH_CLASS - object|false - return new Union([ - new TObject(), - new TFalse(), - ]); - - case 1: // PDO::FETCH_LAZY - object|false - // This actually returns a PDORow object, but that class is - // undocumented, and its attributes are all dynamic anyway - return new Union([ - new TObject(), - new TFalse(), - ]); - - case 11: // PDO::FETCH_NAMED - array>|false - return new Union([ - new TArray([ - Type::getString(), - new Union([ - new TScalar(), - new TNull(), - Type::getListAtomic( - new Union([ - new TScalar(), - new TNull(), - ]), - ), - ]), + ]), + new TFalse(), + ]), + 6 => Type::getBool(), + 7 => new Union([ + new TScalar(), + new TNull(), + new TFalse(), + ]), + 8 => new Union([ + new TObject(), + new TFalse(), + ]), + 1 => new Union([ + new TObject(), + new TFalse(), + ]), + 11 => new Union([ + new TArray([ + Type::getString(), + new Union([ + new TScalar(), + new TNull(), + Type::getListAtomic( + new Union([ + new TScalar(), + new TNull(), + ]), + ), ]), - new TFalse(), - ]); - - case 12: // PDO::FETCH_KEY_PAIR - array - return new Union([ - new TArray([ - Type::getArrayKey(), - new Union([ - new TScalar(), - new TNull(), - ]), + ]), + new TFalse(), + ]), + 12 => new Union([ + new TArray([ + Type::getArrayKey(), + new Union([ + new TScalar(), + new TNull(), ]), - ]); - - case 3: // PDO::FETCH_NUM - list|false - return new Union([ - Type::getListAtomic( - new Union([ - new TScalar(), - new TNull(), - ]), - ), - new TFalse(), - ]); - - case 5: // PDO::FETCH_OBJ - stdClass|false - return new Union([ - new TNamedObject('stdClass'), - new TFalse(), - ]); - } - - return null; + ]), + ]), + 3 => new Union([ + Type::getListAtomic( + new Union([ + new TScalar(), + new TNull(), + ]), + ), + new TFalse(), + ]), + 5 => new Union([ + new TNamedObject('stdClass'), + new TFalse(), + ]), + default => null, + }; } private static function handleFetchAll(MethodReturnTypeProviderEvent $event): ?Union @@ -181,120 +160,101 @@ private static function handleFetchAll(MethodReturnTypeProviderEvent $event): ?U ) { $fetch_class_name = $second_arg_type->getSingleStringLiteral()->value; } - - switch ($fetch_mode) { - case 2: // PDO::FETCH_ASSOC - list> - return new Union([ - Type::getListAtomic( - new Union([ - new TArray([ - Type::getString(), - new Union([ - new TScalar(), - new TNull(), - ]), + return match ($fetch_mode) { + 2 => new Union([ + Type::getListAtomic( + new Union([ + new TArray([ + Type::getString(), + new Union([ + new TScalar(), + new TNull(), ]), ]), - ), - ]); - - case 4: // PDO::FETCH_BOTH - list> - return new Union([ - Type::getListAtomic( - new Union([ - new TArray([ - Type::getArrayKey(), - new Union([ - new TScalar(), - new TNull(), - ]), + ]), + ), + ]), + 4 => new Union([ + Type::getListAtomic( + new Union([ + new TArray([ + Type::getArrayKey(), + new Union([ + new TScalar(), + new TNull(), ]), ]), - ), - ]); - - case 6: // PDO::FETCH_BOUND - list - return new Union([ - Type::getListAtomic( - Type::getBool(), - ), - ]); - - case 7: // PDO::FETCH_COLUMN - list - return new Union([ - Type::getListAtomic( - new Union([ - new TScalar(), - new TNull(), - ]), - ), - ]); - - case 8: // PDO::FETCH_CLASS - list - return new Union([ - Type::getListAtomic( - new Union([ - $fetch_class_name ? new TNamedObject($fetch_class_name) : new TObject(), - ]), - ), - ]); - - case 11: // PDO::FETCH_NAMED - list>> - return new Union([ - Type::getListAtomic( - new Union([ - new TArray([ - Type::getString(), - new Union([ - new TScalar(), - new TNull(), - Type::getListAtomic( - new Union([ - new TScalar(), - new TNull(), - ]), - ), - ]), + ]), + ), + ]), + 6 => new Union([ + Type::getListAtomic( + Type::getBool(), + ), + ]), + 7 => new Union([ + Type::getListAtomic( + new Union([ + new TScalar(), + new TNull(), + ]), + ), + ]), + 8 => new Union([ + Type::getListAtomic( + new Union([ + $fetch_class_name ? new TNamedObject($fetch_class_name) : new TObject(), + ]), + ), + ]), + 11 => new Union([ + Type::getListAtomic( + new Union([ + new TArray([ + Type::getString(), + new Union([ + new TScalar(), + new TNull(), + Type::getListAtomic( + new Union([ + new TScalar(), + new TNull(), + ]), + ), ]), ]), - ), - ]); - - case 12: // PDO::FETCH_KEY_PAIR - array - return new Union([ - new TArray([ - Type::getArrayKey(), - new Union([ - new TScalar(), - new TNull(), - ]), ]), - ]); - - case 3: // PDO::FETCH_NUM - list> - return new Union([ - Type::getListAtomic( - new Union([ - Type::getListAtomic( - new Union([ - new TScalar(), - new TNull(), - ]), - ), - ]), - ), - ]); - - case 5: // PDO::FETCH_OBJ - list - return new Union([ - Type::getListAtomic( - new Union([ - new TNamedObject('stdClass'), - ]), - ), - ]); - } - - return null; + ), + ]), + 12 => new Union([ + new TArray([ + Type::getArrayKey(), + new Union([ + new TScalar(), + new TNull(), + ]), + ]), + ]), + 3 => new Union([ + Type::getListAtomic( + new Union([ + Type::getListAtomic( + new Union([ + new TScalar(), + new TNull(), + ]), + ), + ]), + ), + ]), + 5 => new Union([ + Type::getListAtomic( + new Union([ + new TNamedObject('stdClass'), + ]), + ), + ]), + default => null, + }; } } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php index 91192788b3d..1d3fe3d929f 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/PdoStatementSetFetchMode.php @@ -1,5 +1,7 @@ getSingleStringLiteral()->value, ...$max_dummy); - if ($result === false) { - // the format is invalid - IssueBuffer::maybeAdd( - new InvalidArgument( - 'Argument 1 of ' . $event->getFunctionId() . ' is invalid', - $event->getCodeLocation(), - $event->getFunctionId(), - ), - $statements_source->getSuppressedIssues(), - ); - } else { - IssueBuffer::maybeAdd( - new TooFewArguments( - 'Too few arguments for ' . $event->getFunctionId(), - $event->getCodeLocation(), - $event->getFunctionId(), - ), - $statements_source->getSuppressedIssues(), - ); - } - - return Type::getFalse(); - } - // we can only validate the format and arg 1 when using splat if ($has_splat_args === true) { break; @@ -316,13 +289,13 @@ public static function getFunctionReturnType(FunctionReturnTypeProviderEvent $ev return null; } - if ($initial_result !== null && $initial_result !== false && $initial_result !== '') { + if ($initial_result !== null && $initial_result !== '') { return Type::getNonEmptyString(); } // if we didn't have any valid result // the pattern is invalid or not yet supported by the return type provider - if ($initial_result === null || $initial_result === false) { + if ($initial_result === null) { return null; } diff --git a/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php b/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php index 78ebc6a32fb..35ee134aceb 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/StrReplaceReturnTypeProvider.php @@ -1,5 +1,7 @@ > @@ -82,19 +74,14 @@ final class StatementsProvider */ private array $deletion_ranges = []; - private static ?Emulative $lexer = null; - private static ?Parser $parser = null; public function __construct( - FileProvider $file_provider, - ?ParserCacheProvider $parser_cache_provider = null, - ?FileStorageCacheProvider $file_storage_cache_provider = null + private readonly FileProvider $file_provider, + public ?ParserCacheProvider $parser_cache_provider = null, + private readonly ?FileStorageCacheProvider $file_storage_cache_provider = null, ) { - $this->file_provider = $file_provider; - $this->parser_cache_provider = $parser_cache_provider; $this->this_modified_time = filemtime(__FILE__); - $this->file_storage_cache_provider = $file_storage_cache_provider; } /** @@ -103,7 +90,7 @@ public function __construct( public function getStatementsForFile( string $file_path, int $analysis_php_version_id, - ?Progress $progress = null + ?Progress $progress = null, ): array { unset($this->errors[$file_path]); @@ -120,11 +107,7 @@ public function getStatementsForFile( $config = Config::getInstance(); - if (PHP_VERSION_ID >= 8_01_00) { - $file_content_hash = hash('xxh128', $version . $file_contents); - } else { - $file_content_hash = hash('md4', $version . $file_contents); - } + $file_content_hash = hash('xxh128', $version . $file_contents); if (!$this->parser_cache_provider || (!$config->isInProjectDirs($file_path) && strpos($file_path, 'vendor')) @@ -212,7 +195,7 @@ public function getStatementsForFile( $changed_members = array_map( static function (string $key) use ($file_path_hash): string { - if (strpos($key, 'use:') === 0) { + if (str_starts_with($key, 'use:')) { return $key . ':' . $file_path_hash; } @@ -289,7 +272,7 @@ public function getChangedMembers(): array */ public function addChangedMembers(array $more_changed_members): void { - $this->changed_members = array_merge($more_changed_members, $this->changed_members); + $this->changed_members = [...$more_changed_members, ...$this->changed_members]; } /** @@ -305,7 +288,7 @@ public function getUnchangedSignatureMembers(): array */ public function addUnchangedSignatureMembers(array $more_unchanged_members): void { - $this->unchanged_signature_members = array_merge($more_unchanged_members, $this->unchanged_signature_members); + $this->unchanged_signature_members = [...$more_unchanged_members, ...$this->unchanged_signature_members]; } /** @@ -356,7 +339,7 @@ public function getDeletionRanges(): array */ public function addDiffMap(array $diff_map): void { - $this->diff_map = array_merge($diff_map, $this->diff_map); + $this->diff_map = [...$diff_map, ...$this->diff_map]; } /** @@ -364,7 +347,7 @@ public function addDiffMap(array $diff_map): void */ public function addDeletionRanges(array $deletion_ranges): void { - $this->deletion_ranges = array_merge($deletion_ranges, $this->deletion_ranges); + $this->deletion_ranges = [...$deletion_ranges, ...$this->deletion_ranges]; } public function resetDiffs(): void @@ -388,23 +371,13 @@ public static function parseStatements( ?string $file_path = null, ?string $existing_file_contents = null, ?array $existing_statements = null, - ?array $file_changes = null + ?array $file_changes = null, ): array { - $attributes = [ - 'comments', 'startLine', 'startFilePos', 'endFilePos', - ]; - - if (!self::$lexer) { + if (!self::$parser) { $major_version = Codebase::transformPhpVersionId($analysis_php_version_id, 10_000); $minor_version = Codebase::transformPhpVersionId($analysis_php_version_id % 10_000, 100); - self::$lexer = new Emulative([ - 'usedAttributes' => $attributes, - 'phpVersion' => $major_version . '.' . $minor_version, - ]); - } - - if (!self::$parser) { - self::$parser = (new PhpParser\ParserFactory())->create(PhpParser\ParserFactory::ONLY_PHP7, self::$lexer); + $php_version = PhpVersion::fromComponents($major_version, $minor_version); + self::$parser = (new PhpParser\ParserFactory())->createForVersion($php_version); } $used_cached_statements = false; @@ -430,7 +403,7 @@ public static function parseStatements( try { /** @var list */ $stmts = self::$parser->parse($file_contents, $error_handler) ?: []; - } catch (Throwable $t) { + } catch (Throwable) { $stmts = []; // hope this got caught below @@ -440,7 +413,7 @@ public static function parseStatements( try { /** @var list */ $stmts = self::$parser->parse($file_contents, $error_handler) ?: []; - } catch (Throwable $t) { + } catch (Throwable) { $stmts = []; // hope this got caught below @@ -481,11 +454,6 @@ public static function parseStatements( return $stmts; } - public static function clearLexer(): void - { - self::$lexer = null; - } - public static function clearParser(): void { self::$parser = null; diff --git a/src/Psalm/Internal/ReferenceConstraint.php b/src/Psalm/Internal/ReferenceConstraint.php index 74bfb4eef88..d318bdaa6f6 100644 --- a/src/Psalm/Internal/ReferenceConstraint.php +++ b/src/Psalm/Internal/ReferenceConstraint.php @@ -1,5 +1,7 @@ $line) { - if (strpos($line, '@') !== false && preg_match('/^ *\*?\s*@\w/', $line)) { + if (str_contains($line, '@') && preg_match('/^ *\*?\s*@\w/', $line)) { $last = $k; } elseif (trim($line) === '') { $last = false; @@ -101,8 +106,8 @@ public static function parse(string $docblock, int $offsetStart): ParsedDocblock [$type] = $type_info; [$data, $data_offset] = $data_info; - if (strpos($data, '*') !== false) { - $data = rtrim(preg_replace('/^ *\*\s*$/m', '', $data)); + if (str_contains($data, '*')) { + $data = rtrim((string) preg_replace('/^ *\*\s*$/m', '', $data)); } if (empty($special[$type])) { diff --git a/src/Psalm/Internal/Scanner/FileScanner.php b/src/Psalm/Internal/Scanner/FileScanner.php index 4c1a7033927..d8ba100c47f 100644 --- a/src/Psalm/Internal/Scanner/FileScanner.php +++ b/src/Psalm/Internal/Scanner/FileScanner.php @@ -1,5 +1,7 @@ file_path = $file_path; - $this->file_name = $file_name; - $this->will_analyze = $will_analyze; } public function scan( Codebase $codebase, FileStorage $file_storage, bool $storage_from_cache = false, - ?Progress $progress = null + ?Progress $progress = null, ): void { if ($progress === null) { $progress = new VoidProgress(); diff --git a/src/Psalm/Internal/Scanner/FunctionDocblockComment.php b/src/Psalm/Internal/Scanner/FunctionDocblockComment.php index 959cfb3231f..82d8d96f4a2 100644 --- a/src/Psalm/Internal/Scanner/FunctionDocblockComment.php +++ b/src/Psalm/Internal/Scanner/FunctionDocblockComment.php @@ -1,5 +1,7 @@ > */ - public array $tags = []; - /** @var array> */ public array $combined_tags = []; private static bool $shouldAddNewLineBetweenAnnotations = true; /** @param array> $tags */ - public function __construct(string $description, array $tags, string $first_line_padding = '') + public function __construct(public string $description, public array $tags, public string $first_line_padding = '') { - $this->description = $description; - $this->tags = $tags; - $this->first_line_padding = $first_line_padding; } public function render(string $left_padding): string diff --git a/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php b/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php index 6058c39ccbd..ed5f7e92800 100644 --- a/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php +++ b/src/Psalm/Internal/Scanner/PhpStormMetaScanner.php @@ -1,5 +1,7 @@ value->name->getParts() === ['type'] && $args[1]->value->getArgs() - && $args[1]->value->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + && $args[1]->value->getArgs()[0]->value instanceof PhpParser\Node\Scalar\Int_ ) { $type_offset = $args[1]->value->getArgs()[0]->value->value; } @@ -114,7 +116,7 @@ public static function handleOverride(array $args, Codebase $codebase): void if ($args[1]->value->name->getParts() === ['elementType'] && $args[1]->value->getArgs() - && $args[1]->value->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + && $args[1]->value->getArgs()[0]->value instanceof PhpParser\Node\Scalar\Int_ ) { $element_type_offset = $args[1]->value->getArgs()[0]->value->value; } @@ -124,7 +126,7 @@ public static function handleOverride(array $args, Codebase $codebase): void && $identifier->name instanceof PhpParser\Node\Identifier && ( $identifier->getArgs() === [] - || $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + || $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\Int_ ) ) { $meta_fq_classlike_name = $identifier->class->toString(); @@ -134,7 +136,7 @@ public static function handleOverride(array $args, Codebase $codebase): void if ($map) { $offset = 0; if ($identifier->getArgs() - && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\Int_ ) { $offset = $identifier->getArgs()[0]->value->value; } @@ -142,12 +144,12 @@ public static function handleOverride(array $args, Codebase $codebase): void $codebase->methods->return_type_provider->registerClosure( $meta_fq_classlike_name, static function ( - MethodReturnTypeProviderEvent $event + MethodReturnTypeProviderEvent $event, ) use ( $map, $offset, $meta_fq_classlike_name, - $meta_method_name + $meta_method_name, ): ?Union { $statements_analyzer = $event->getSource(); $call_args = $event->getCallArgs(); @@ -176,10 +178,10 @@ static function ( } if (($mapped_type = $map[''] ?? null) && is_string($mapped_type)) { - if (strpos($mapped_type, '@') !== false) { + if (str_contains($mapped_type, '@')) { $mapped_type = str_replace('@', $offset_arg_value, $mapped_type); - if (strpos($mapped_type, '.') === false) { + if (!str_contains($mapped_type, '.')) { return new Union([ new TNamedObject($mapped_type), ]); @@ -195,11 +197,11 @@ static function ( $codebase->methods->return_type_provider->registerClosure( $meta_fq_classlike_name, static function ( - MethodReturnTypeProviderEvent $event + MethodReturnTypeProviderEvent $event, ) use ( $type_offset, $meta_fq_classlike_name, - $meta_method_name + $meta_method_name, ): ?Union { $statements_analyzer = $event->getSource(); $call_args = $event->getCallArgs(); @@ -229,11 +231,11 @@ static function ( $codebase->methods->return_type_provider->registerClosure( $meta_fq_classlike_name, static function ( - MethodReturnTypeProviderEvent $event + MethodReturnTypeProviderEvent $event, ) use ( $element_type_offset, $meta_fq_classlike_name, - $meta_method_name + $meta_method_name, ): ?Union { $statements_analyzer = $event->getSource(); $call_args = $event->getCallArgs(); @@ -276,7 +278,7 @@ static function ( && $identifier->name instanceof PhpParser\Node\Name\FullyQualified && ( $identifier->getArgs() === [] - || $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + || $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\Int_ ) ) { $function_id = strtolower($identifier->name->toString()); @@ -284,7 +286,7 @@ static function ( if ($map) { $offset = 0; if ($identifier->getArgs() - && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\LNumber + && $identifier->getArgs()[0]->value instanceof PhpParser\Node\Scalar\Int_ ) { $offset = $identifier->getArgs()[0]->value->value; } @@ -292,10 +294,10 @@ static function ( $codebase->functions->return_type_provider->registerClosure( $function_id, static function ( - FunctionReturnTypeProviderEvent $event + FunctionReturnTypeProviderEvent $event, ) use ( $map, - $offset + $offset, ): Union { $statements_analyzer = $event->getStatementsSource(); $call_args = $event->getCallArgs(); @@ -318,10 +320,10 @@ static function ( } if (($mapped_type = $map[''] ?? null) && is_string($mapped_type)) { - if (strpos($mapped_type, '@') !== false) { + if (str_contains($mapped_type, '@')) { $mapped_type = str_replace('@', $offset_arg_value, $mapped_type); - if (strpos($mapped_type, '.') === false) { + if (!str_contains($mapped_type, '.')) { return new Union([ new TNamedObject($mapped_type), ]); @@ -342,9 +344,9 @@ static function ( $codebase->functions->return_type_provider->registerClosure( $function_id, static function ( - FunctionReturnTypeProviderEvent $event + FunctionReturnTypeProviderEvent $event, ) use ( - $type_offset + $type_offset, ): Union { $statements_analyzer = $event->getStatementsSource(); $call_args = $event->getCallArgs(); @@ -372,9 +374,9 @@ static function ( $codebase->functions->return_type_provider->registerClosure( $function_id, static function ( - FunctionReturnTypeProviderEvent $event + FunctionReturnTypeProviderEvent $event, ) use ( - $element_type_offset + $element_type_offset, ): Union { $statements_analyzer = $event->getStatementsSource(); $call_args = $event->getCallArgs(); diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php index 7efbfb48c77..c3b11b3061e 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayOffsetFetch.php @@ -1,5 +1,7 @@ array = $left; - $this->offset = $right; + public function __construct( + public readonly UnresolvedConstantComponent $array, + public readonly UnresolvedConstantComponent $offset, + ) { } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php index ef8c443d7ba..ca03b3a54db 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArraySpread.php @@ -1,5 +1,7 @@ array = $array; } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php index 6fc943daa79..6aa4df4008a 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ArrayValue.php @@ -1,5 +1,7 @@ */ - public array $entries; - /** @param list $entries */ - public function __construct(array $entries) + public function __construct(public readonly array $entries) { - $this->entries = $entries; } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php index 101c18a48ea..5833c76a45c 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ClassConstant.php @@ -1,5 +1,7 @@ fqcln = $fqcln; - $this->name = $name; } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php index 45c36ee7bf7..250f03a1b87 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/Constant.php @@ -1,5 +1,7 @@ name = $name; - $this->is_fully_qualified = $is_fully_qualified; } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php index 5b56cc696ed..0dea14889f7 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumNameFetch.php @@ -1,5 +1,7 @@ fqcln = $fqcln; - $this->case = $case; } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php index 4387213787b..26efbf10ec0 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/EnumValueFetch.php @@ -1,5 +1,7 @@ key = $key; - $this->value = $value; + public function __construct( + public readonly ?UnresolvedConstantComponent $key, + public readonly UnresolvedConstantComponent $value, + ) { } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php index 6e1f292cec9..73d12f96f7d 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/ScalarValue.php @@ -1,5 +1,7 @@ value = $value; } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php index 21637ee839f..d2bd967d51a 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedAdditionOp.php @@ -1,5 +1,7 @@ left = $left; - $this->right = $right; + public function __construct( + public readonly UnresolvedConstantComponent $left, + public readonly UnresolvedConstantComponent $right, + ) { } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php index 1b8e4ae018b..c5664765620 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstant/UnresolvedBitwiseAnd.php @@ -1,5 +1,7 @@ cond = $cond; - $this->if = $if; - $this->else = $else; } } diff --git a/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php b/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php index caede827d93..9046813131f 100644 --- a/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php +++ b/src/Psalm/Internal/Scanner/UnresolvedConstantComponent.php @@ -1,5 +1,7 @@ |null */ public ?array $break_vars = null; - public function __construct(Context $parent_context) + public function __construct(public Context $parent_context) { - $this->parent_context = $parent_context; } public function __destruct() diff --git a/src/Psalm/Internal/Scope/FinallyScope.php b/src/Psalm/Internal/Scope/FinallyScope.php index 816ed49904e..a587c385bf9 100644 --- a/src/Psalm/Internal/Scope/FinallyScope.php +++ b/src/Psalm/Internal/Scope/FinallyScope.php @@ -1,5 +1,7 @@ - */ - public array $vars_in_scope = []; - /** * @param array $vars_in_scope */ - public function __construct(array $vars_in_scope) + public function __construct(public array $vars_in_scope) { - $this->vars_in_scope = $vars_in_scope; } } diff --git a/src/Psalm/Internal/Scope/IfConditionalScope.php b/src/Psalm/Internal/Scope/IfConditionalScope.php index acd923a58f7..4db024f4c22 100644 --- a/src/Psalm/Internal/Scope/IfConditionalScope.php +++ b/src/Psalm/Internal/Scope/IfConditionalScope.php @@ -1,5 +1,7 @@ - */ - public array $cond_referenced_var_ids; - - /** - * @var array - */ - public array $assigned_in_conditional_var_ids; - - /** @var list */ - public array $entry_clauses; - /** * @param array $cond_referenced_var_ids * @param array $assigned_in_conditional_var_ids * @param list $entry_clauses */ public function __construct( - Context $if_context, - Context $post_if_context, - array $cond_referenced_var_ids, - array $assigned_in_conditional_var_ids, - array $entry_clauses + public Context $if_context, + public Context $post_if_context, + public array $cond_referenced_var_ids, + public array $assigned_in_conditional_var_ids, + public array $entry_clauses, ) { - $this->if_context = $if_context; - $this->post_if_context = $post_if_context; - $this->cond_referenced_var_ids = $cond_referenced_var_ids; - $this->assigned_in_conditional_var_ids = $assigned_in_conditional_var_ids; - $this->entry_clauses = $entry_clauses; } } diff --git a/src/Psalm/Internal/Scope/IfScope.php b/src/Psalm/Internal/Scope/IfScope.php index 4d75ee24462..955aaffdf03 100644 --- a/src/Psalm/Internal/Scope/IfScope.php +++ b/src/Psalm/Internal/Scope/IfScope.php @@ -1,5 +1,7 @@ */ @@ -51,10 +49,8 @@ final class LoopScope */ public array $final_actions = []; - public function __construct(Context $loop_context, Context $parent_context) + public function __construct(public Context $loop_context, public Context $loop_parent_context) { - $this->loop_context = $loop_context; - $this->loop_parent_context = $parent_context; } public function __destruct() diff --git a/src/Psalm/Internal/Scope/SwitchScope.php b/src/Psalm/Internal/Scope/SwitchScope.php index c5f5938ebf1..37d216da92a 100644 --- a/src/Psalm/Internal/Scope/SwitchScope.php +++ b/src/Psalm/Internal/Scope/SwitchScope.php @@ -1,5 +1,7 @@ visibility === ClassLikeAnalyzer::VISIBILITY_PUBLIC - ? PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC + ? PhpParser\Modifiers::PUBLIC : ($constant_storage->visibility === ClassLikeAnalyzer::VISIBILITY_PROTECTED - ? PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED - : PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE) + ? PhpParser\Modifiers::PROTECTED + : PhpParser\Modifiers::PRIVATE) ); } @@ -162,17 +162,11 @@ private static function getPropertyNodes(ClassLikeStorage $storage): array $property_nodes = []; foreach ($storage->properties as $property_name => $property_storage) { - switch ($property_storage->visibility) { - case ClassLikeAnalyzer::VISIBILITY_PRIVATE: - $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE; - break; - case ClassLikeAnalyzer::VISIBILITY_PROTECTED: - $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED; - break; - default: - $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC; - break; - } + $flag = match ($property_storage->visibility) { + ClassLikeAnalyzer::VISIBILITY_PRIVATE => PhpParser\Modifiers::PRIVATE, + ClassLikeAnalyzer::VISIBILITY_PROTECTED => PhpParser\Modifiers::PROTECTED, + default => PhpParser\Modifiers::PUBLIC, + }; $docblock = new ParsedDocblock('', []); @@ -188,9 +182,9 @@ private static function getPropertyNodes(ClassLikeStorage $storage): array } $property_nodes[] = new VirtualProperty( - $flag | ($property_storage->is_static ? PhpParser\Node\Stmt\Class_::MODIFIER_STATIC : 0), + $flag | ($property_storage->is_static ? PhpParser\Modifiers::STATIC : 0), [ - new VirtualPropertyProperty( + new VirtualPropertyItem( $property_name, $property_storage->suggested_type ? StubsGenerator::getExpressionFromType($property_storage->suggested_type) @@ -227,17 +221,11 @@ private static function getMethodNodes(ClassLikeStorage $storage): array { throw new UnexpectedValueException('very bad'); } - switch ($method_storage->visibility) { - case ReflectionProperty::IS_PRIVATE: - $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PRIVATE; - break; - case ReflectionProperty::IS_PROTECTED: - $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PROTECTED; - break; - default: - $flag = PhpParser\Node\Stmt\Class_::MODIFIER_PUBLIC; - break; - } + $flag = match ($method_storage->visibility) { + ReflectionProperty::IS_PRIVATE => PhpParser\Modifiers::PRIVATE, + ReflectionProperty::IS_PROTECTED => PhpParser\Modifiers::PROTECTED, + default => PhpParser\Modifiers::PUBLIC, + }; $docblock = new ParsedDocblock('', []); @@ -288,8 +276,8 @@ private static function getMethodNodes(ClassLikeStorage $storage): array { $method_storage->cased_name, [ 'flags' => $flag - | ($method_storage->is_static ? PhpParser\Node\Stmt\Class_::MODIFIER_STATIC : 0) - | ($method_storage->abstract ? PhpParser\Node\Stmt\Class_::MODIFIER_ABSTRACT : 0), + | ($method_storage->is_static ? PhpParser\Modifiers::STATIC : 0) + | ($method_storage->abstract ? PhpParser\Modifiers::ABSTRACT : 0), 'params' => StubsGenerator::getFunctionParamNodes($method_storage), 'returnType' => $method_storage->signature_return_type ? StubsGenerator::getParserTypeFromPsalmType($method_storage->signature_return_type) diff --git a/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php b/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php index 95909902309..19e212854b9 100644 --- a/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php +++ b/src/Psalm/Internal/Stubs/Generator/StubsGenerator.php @@ -1,4 +1,4 @@ -getAll() as $storage) { - if (strpos($storage->name, 'Psalm\\') === 0) { + if (str_starts_with($storage->name, 'Psalm\\')) { continue; } if ($storage->location - && strpos($storage->location->file_path, $psalm_base) === 0 + && str_starts_with($storage->location->file_path, $psalm_base) ) { continue; } @@ -97,7 +97,7 @@ public static function getAll( foreach ($codebase->functions->getAllStubbedFunctions() as $function_storage) { if ($function_storage->location - && strpos($function_storage->location->file_path, $psalm_base) === 0 + && str_starts_with($function_storage->location->file_path, $psalm_base) ) { continue; } @@ -143,7 +143,7 @@ public static function getAll( } foreach ($file_provider->getAll() as $file_storage) { - if (strpos($file_storage->file_path, $psalm_base) === 0) { + if (str_starts_with($file_storage->file_path, $psalm_base)) { continue; } @@ -365,11 +365,11 @@ public static function getExpressionFromType(Union $type) : PhpParser\Node\Expr } if ($atomic_type instanceof TLiteralInt) { - return new VirtualLNumber($atomic_type->value); + return new VirtualInt($atomic_type->value); } if ($atomic_type instanceof TLiteralFloat) { - return new VirtualDNumber($atomic_type->value); + return new VirtualFloat($atomic_type->value); } if ($atomic_type instanceof TFalse) { @@ -395,7 +395,7 @@ public static function getExpressionFromType(Union $type) : PhpParser\Node\Expr if ($atomic_type->is_list) { $key_type = null; } elseif (is_int($property_name)) { - $key_type = new VirtualLNumber($property_name); + $key_type = new VirtualInt($property_name); } else { $key_type = new VirtualString($property_name); } diff --git a/src/Psalm/Internal/Type/ArrayType.php b/src/Psalm/Internal/Type/ArrayType.php index f115fcc9879..a0a9cebb2c9 100644 --- a/src/Psalm/Internal/Type/ArrayType.php +++ b/src/Psalm/Internal/Type/ArrayType.php @@ -17,20 +17,8 @@ */ final class ArrayType { - public Union $key; - - public Union $value; - - public bool $is_list; - - public ?int $count = null; - - public function __construct(Union $key, Union $value, bool $is_list, ?int $count) + public function __construct(public Union $key, public Union $value, public bool $is_list, public ?int $count) { - $this->key = $key; - $this->value = $value; - $this->is_list = $is_list; - $this->count = $count; } /** diff --git a/src/Psalm/Internal/Type/AssertionReconciler.php b/src/Psalm/Internal/Type/AssertionReconciler.php index d82a5c07384..851dc29c1c6 100644 --- a/src/Psalm/Internal/Type/AssertionReconciler.php +++ b/src/Psalm/Internal/Type/AssertionReconciler.php @@ -1,5 +1,7 @@ getCodebase(); @@ -238,7 +239,7 @@ public static function reconcile( private static function getMissingType( Assertion $assertion, - bool $inside_loop + bool $inside_loop, ): Union { if (($assertion instanceof IsIsset || $assertion instanceof IsEqualIsset) || $assertion instanceof NonEmpty @@ -282,7 +283,7 @@ private static function refine( bool $negated, ?CodeLocation $code_location, array $suppressed_issues, - int &$failed_reconciliation + int &$failed_reconciliation, ): Union { $codebase = $statements_analyzer->getCodebase(); @@ -522,7 +523,7 @@ private static function filterTypeWithAnother( Codebase $codebase, Union &$existing_type, Union $new_type, - bool &$any_scalar_type_match_found = false + bool &$any_scalar_type_match_found = false, ): ?Union { $matching_atomic_types = []; @@ -555,7 +556,7 @@ private static function filterAtomicWithAnother( Atomic &$type_1_atomic, Atomic $type_2_atomic, Codebase $codebase, - bool &$any_scalar_type_match_found + bool &$any_scalar_type_match_found, ): ?Atomic { if ($type_1_atomic instanceof TFloat && $type_2_atomic instanceof TInt @@ -619,7 +620,6 @@ private static function filterAtomicWithAnother( } /*if ($type_2_atomic instanceof TKeyedArray - && $type_1_atomic instanceof \Psalm\Type\Atomic\TList ) { $type_2_key = $type_2_atomic->getGenericKeyType(); $type_2_value = $type_2_atomic->getGenericValueType(); @@ -826,10 +826,10 @@ private static function refineContainedAtomicWithAnother( Atomic $type_1_atomic, Atomic $type_2_atomic, Codebase $codebase, - bool $type_coerced + bool $type_coerced, ): ?Atomic { if ($type_coerced - && get_class($type_2_atomic) === TNamedObject::class + && $type_2_atomic::class === TNamedObject::class && $type_1_atomic instanceof TGenericObject ) { // this is a hack - it's not actually rigorous, as the params may be different @@ -871,7 +871,7 @@ private static function handleLiteralEquality( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $existing_var_atomic_types = []; @@ -951,7 +951,7 @@ private static function handleLiteralEquality( $existing_var_type = $existing_var_type->getBuilder(); foreach ($existing_var_atomic_types as $atomic_key => $atomic_type) { - if (get_class($atomic_type) === TNamedObject::class + if ($atomic_type::class === TNamedObject::class && $atomic_type->value === $fq_enum_name ) { $can_be_equal = true; @@ -1010,7 +1010,7 @@ private static function handleLiteralEqualityWithInt( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $value = $assertion_type->value; @@ -1151,7 +1151,7 @@ private static function handleLiteralEqualityWithString( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $value = $assertion_type->value; @@ -1294,7 +1294,7 @@ private static function handleLiteralEqualityWithFloat( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $value = $assertion_type->value; @@ -1429,7 +1429,7 @@ private static function getCompatibleIntType( Union $existing_var_type, array $existing_var_atomic_types, TLiteralInt $assertion_type, - bool $is_loose_equality + bool $is_loose_equality, ): ?Union { foreach ($existing_var_atomic_types as $existing_var_atomic_type) { if ($existing_var_atomic_type instanceof TMixed @@ -1457,7 +1457,7 @@ private static function getCompatibleStringType( Union $existing_var_type, array $existing_var_atomic_types, TLiteralString $assertion_type, - bool $is_loose_equality + bool $is_loose_equality, ): ?Union { foreach ($existing_var_atomic_types as $existing_var_atomic_type) { if ($existing_var_atomic_type instanceof TMixed @@ -1484,7 +1484,7 @@ private static function getCompatibleFloatType( Union $existing_var_type, array $existing_var_atomic_types, TLiteralFloat $assertion_type, - bool $is_loose_equality + bool $is_loose_equality, ): ?Union { foreach ($existing_var_atomic_types as $existing_var_atomic_type) { if ($existing_var_atomic_type instanceof TMixed @@ -1515,7 +1515,7 @@ private static function handleIsA( ?CodeLocation $code_location, ?string $key, array $suppressed_issues, - bool &$should_return + bool &$should_return, ): array { $allow_string_comparison = $assertion->allow_string; @@ -1546,7 +1546,7 @@ private static function handleIsA( if ($assertion_type instanceof TTemplateParamClass) { return [new TTemplateParam( $assertion_type->param_name, - new Union([$assertion_type->as_type ? $assertion_type->as_type : new TObject()]), + new Union([$assertion_type->as_type ?: new TObject()]), $assertion_type->defining_class, )]; } diff --git a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php index 122bc65d70e..8a39b72c976 100644 --- a/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ArrayTypeComparator.php @@ -1,5 +1,7 @@ is_list - && !$container_type_part->isNonEmpty() - && !$container_type_part->isSealed() - && $input_type_part->equals( - $container_type_part->getGenericArrayType($container_type_part->isNonEmpty()), - false, - ) - ) { - return true; - } - if ($container_type_part instanceof TKeyedArray && $input_type_part instanceof TArray ) { diff --git a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php index 15c4512429f..48369076150 100644 --- a/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/AtomicTypeComparator.php @@ -1,5 +1,7 @@ getKeyedArray(); - } - if ($container_type_part instanceof TList) { - $container_type_part = $container_type_part->getKeyedArray(); - } + + if (($container_type_part instanceof TTemplateParam || ($container_type_part instanceof TNamedObject && $container_type_part->extra_types)) @@ -116,10 +112,10 @@ public static function isContainedBy( && !$container_type_part->extra_types && $input_type_part instanceof TMixed) ) { - if (get_class($input_type_part) === TMixed::class + if ($input_type_part::class === TMixed::class && ( - get_class($container_type_part) === TEmptyMixed::class - || get_class($container_type_part) === TNonEmptyMixed::class + $container_type_part::class === TEmptyMixed::class + || $container_type_part::class === TNonEmptyMixed::class ) ) { if ($atomic_comparison_result) { @@ -316,14 +312,14 @@ public static function isContainedBy( ); } - if (get_class($container_type_part) === TNamedObject::class + if ($container_type_part::class === TNamedObject::class && $input_type_part instanceof TEnumCase && $input_type_part->value === $container_type_part->value ) { return true; } - if (get_class($input_type_part) === TNamedObject::class + if ($input_type_part::class === TNamedObject::class && $container_type_part instanceof TEnumCase && $input_type_part->value === $container_type_part->value ) { @@ -386,8 +382,8 @@ public static function isContainedBy( return true; } - if (get_class($input_type_part) === TObject::class - && get_class($container_type_part) === TObject::class + if ($input_type_part::class === TObject::class + && $container_type_part::class === TObject::class ) { return true; } @@ -806,14 +802,10 @@ public static function canBeIdentical( Codebase $codebase, Atomic $type1_part, Atomic $type2_part, - bool $allow_interface_equality = true + bool $allow_interface_equality = true, ): bool { - if ($type1_part instanceof TList) { - $type1_part = $type1_part->getKeyedArray(); - } - if ($type2_part instanceof TList) { - $type2_part = $type2_part->getKeyedArray(); - } + + if ((self::isLegacyTListLike($type1_part) && self::isLegacyTNonEmptyListLike($type2_part)) || (self::isLegacyTListLike($type2_part) @@ -828,9 +820,9 @@ public static function canBeIdentical( ); } - if ((get_class($type1_part) === TArray::class + if (($type1_part::class === TArray::class && $type2_part instanceof TNonEmptyArray) - || (get_class($type2_part) === TArray::class + || ($type2_part::class === TArray::class && $type1_part instanceof TNonEmptyArray) ) { return UnionTypeComparator::canExpressionTypesBeIdentical( diff --git a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php index 7573743acc8..8d79a41037f 100644 --- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -1,5 +1,7 @@ getKeyedArray(); - } + if ($input_type_part instanceof TArray) { if ($input_type_part->type_params[1]->isMixed() || $input_type_part->type_params[1]->hasScalar() @@ -209,15 +206,6 @@ public static function isNotExplicitlyCallableTypeCallable( if (!$input_type_part->type_params[1]->hasString()) { return false; } - - if (!$input_type_part instanceof TCallableArray) { - if ($atomic_comparison_result) { - $atomic_comparison_result->type_coerced_from_mixed = true; - $atomic_comparison_result->type_coerced = true; - } - - return false; - } } elseif ($input_type_part instanceof TKeyedArray) { $method_id = self::getCallableMethodIdFromTKeyedArray($input_type_part); @@ -239,7 +227,7 @@ public static function isNotExplicitlyCallableTypeCallable( if (!$codebase->methods->hasStorage($method_id)) { return false; } - } catch (Exception $e) { + } catch (Exception) { return false; } } @@ -269,11 +257,9 @@ public static function getCallableFromAtomic( Atomic $input_type_part, ?TCallable $container_type_part = null, ?StatementsAnalyzer $statements_analyzer = null, - bool $expand_callable = false + bool $expand_callable = false, ): ?Atomic { - if ($input_type_part instanceof TList) { - $input_type_part = $input_type_part->getKeyedArray(); - } + if ($input_type_part instanceof TCallable || $input_type_part instanceof TClosure) { return $input_type_part; } @@ -336,7 +322,7 @@ public static function getCallableFromAtomic( $return_type, $function_storage->pure, ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { if (InternalCallMapHandler::inCallMap($input_type_part->value)) { $args = []; @@ -401,7 +387,7 @@ public static function getCallableFromAtomic( $converted_return_type, $method_storage->pure, ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { // do nothing } } @@ -491,8 +477,8 @@ public static function getCallableMethodIdFromTKeyedArray( TKeyedArray $input_type_part, ?Codebase $codebase = null, ?string $calling_method_id = null, - ?string $file_name = null - ) { + ?string $file_name = null, + ): string|MethodIdentifier|null { if (!isset($input_type_part->properties[0]) || !isset($input_type_part->properties[1]) || count($input_type_part->properties) > 2 diff --git a/src/Psalm/Internal/Type/Comparator/ClassLikeStringComparator.php b/src/Psalm/Internal/Type/Comparator/ClassLikeStringComparator.php index 0fe514cc300..1c9b4ed1ab9 100644 --- a/src/Psalm/Internal/Type/Comparator/ClassLikeStringComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ClassLikeStringComparator.php @@ -1,5 +1,7 @@ type_coerced = true; diff --git a/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php b/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php index fe91a3bc003..aea8be1da1a 100644 --- a/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/GenericTypeComparator.php @@ -1,5 +1,7 @@ min_bound === null; $is_input_max = $input_type_part->max_bound === null; @@ -47,7 +48,7 @@ public static function isContainedBy( */ public static function isContainedByUnion( TIntRange $input_type_part, - Union $container_type + Union $container_type, ): bool { $container_atomic_types = $container_type->getAtomicTypes(); $reduced_range = new TIntRange( @@ -57,11 +58,11 @@ public static function isContainedByUnion( ); if (isset($container_atomic_types['int'])) { - if (get_class($container_atomic_types['int']) === TInt::class) { + if ($container_atomic_types['int']::class === TInt::class) { return true; } - if (get_class($container_atomic_types['int']) === TNonspecificLiteralInt::class) { + if ($container_atomic_types['int']::class === TNonspecificLiteralInt::class) { return true; } diff --git a/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php b/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php index 1fad958847d..f9786880a27 100644 --- a/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php +++ b/src/Psalm/Internal/Type/Comparator/KeyedArrayComparator.php @@ -1,5 +1,7 @@ fallback_params === null; @@ -285,7 +287,7 @@ public static function isContainedByObjectWithProperties( TNamedObject $input_type_part, TObjectWithProperties $container_type_part, bool $allow_interface_equality, - ?TypeComparisonResult $atomic_comparison_result + ?TypeComparisonResult $atomic_comparison_result, ): bool { $all_types_contain = true; @@ -346,7 +348,7 @@ public static function isContainedByObjectWithProperties( public static function coerceToObjectWithProperties( Codebase $codebase, TNamedObject $input_type_part, - TObjectWithProperties $container_type_part + TObjectWithProperties $container_type_part, ): ?TObjectWithProperties { $storage = $codebase->classlikes->getStorageFor($input_type_part->value); diff --git a/src/Psalm/Internal/Type/Comparator/ObjectComparator.php b/src/Psalm/Internal/Type/Comparator/ObjectComparator.php index 88b239efb06..73454ae8a88 100644 --- a/src/Psalm/Internal/Type/Comparator/ObjectComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ObjectComparator.php @@ -1,5 +1,7 @@ defining_class != $input_type_part->defining_class && 1 == count($container_type_part->as->getAtomicTypes()) && 1 == count($input_type_part->as->getAtomicTypes())) { - $containerDefinedInFunction = strpos($container_type_part->defining_class, 'fn-') === 0; - $inputDefinedInFunction = strpos($input_type_part->defining_class, 'fn-') === 0; + $containerDefinedInFunction = str_starts_with($container_type_part->defining_class, 'fn-'); + $inputDefinedInFunction = str_starts_with($input_type_part->defining_class, 'fn-'); if ($inputDefinedInFunction) { $separatorPos = strpos($input_type_part->defining_class, '::'); if ($separatorPos === false) { @@ -179,17 +182,17 @@ private static function isIntersectionShallowlyContainedBy( ?string $intersection_container_type_lower, bool $container_was_static, bool $allow_interface_equality, - ?TypeComparisonResult $atomic_comparison_result + ?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 (str_starts_with($intersection_container_type->defining_class, 'fn-') + || str_starts_with($intersection_input_type->defining_class, 'fn-') ) { - if (strpos($intersection_input_type->defining_class, 'fn-') === 0 - && strpos($intersection_container_type->defining_class, 'fn-') === 0 + if (str_starts_with($intersection_input_type->defining_class, 'fn-') + && str_starts_with($intersection_container_type->defining_class, 'fn-') && $intersection_input_type->defining_class !== $intersection_container_type->defining_class ) { @@ -213,11 +216,11 @@ private static function isIntersectionShallowlyContainedBy( 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) + && !str_starts_with($intersection_input_type->defining_class, 'fn-') + && !str_starts_with($intersection_container_type->defining_class, 'fn-')) ) { - if (strpos($intersection_input_type->defining_class, 'fn-') === 0 - || strpos($intersection_container_type->defining_class, 'fn-') === 0 + if (str_starts_with($intersection_input_type->defining_class, 'fn-') + || str_starts_with($intersection_container_type->defining_class, 'fn-') ) { return false; } diff --git a/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php b/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php index f422285413e..1f5c9216c6d 100644 --- a/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php @@ -1,5 +1,7 @@ type_coerced = true; @@ -122,14 +123,14 @@ public static function isContainedBy( } if ($input_type_part instanceof TCallableString) { - if (get_class($container_type_part) === TNonEmptyString::class - || get_class($container_type_part) === TNonFalsyString::class + if ($container_type_part::class === TNonEmptyString::class + || $container_type_part::class === TNonFalsyString::class ) { return true; } - if (get_class($container_type_part) === TLowercaseString::class - || get_class($container_type_part) === TSingleLetter::class + if ($container_type_part::class === TLowercaseString::class + || $container_type_part::class === TSingleLetter::class ) { if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; @@ -292,12 +293,12 @@ public static function isContainedBy( return true; } - if (get_class($container_type_part) === TFloat::class && $input_type_part instanceof TLiteralFloat) { + if ($container_type_part::class === TFloat::class && $input_type_part instanceof TLiteralFloat) { return true; } - if ((get_class($container_type_part) === TNonEmptyString::class - || get_class($container_type_part) === TNonEmptyNonspecificLiteralString::class) + if (($container_type_part::class === TNonEmptyString::class + || $container_type_part::class === TNonEmptyNonspecificLiteralString::class) && $input_type_part instanceof TNonFalsyString ) { return true; @@ -334,15 +335,15 @@ public static function isContainedBy( return false; } - if ((get_class($container_type_part) === TNonEmptyString::class - || get_class($container_type_part) === TNonFalsyString::class - || get_class($container_type_part) === TSingleLetter::class) + if (($container_type_part::class === TNonEmptyString::class + || $container_type_part::class === TNonFalsyString::class + || $container_type_part::class === TSingleLetter::class) && $input_type_part instanceof TLiteralString ) { return true; } - if (get_class($input_type_part) === TInt::class && $container_type_part instanceof TLiteralInt) { + if ($input_type_part::class === TInt::class && $container_type_part instanceof TLiteralInt) { if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; $atomic_comparison_result->type_coerced_from_scalar = true; @@ -379,7 +380,7 @@ public static function isContainedBy( return false; } - if (get_class($input_type_part) === TFloat::class && $container_type_part instanceof TLiteralFloat) { + if ($input_type_part::class === TFloat::class && $container_type_part instanceof TLiteralFloat) { if ($atomic_comparison_result) { $atomic_comparison_result->type_coerced = true; $atomic_comparison_result->type_coerced_from_scalar = true; @@ -388,8 +389,8 @@ public static function isContainedBy( return false; } - if ((get_class($input_type_part) === TString::class - || get_class($input_type_part) === TSingleLetter::class + if (($input_type_part::class === TString::class + || $input_type_part::class === TSingleLetter::class || $input_type_part instanceof TNonEmptyString || $input_type_part instanceof TNonspecificLiteralString) && $container_type_part instanceof TLiteralString @@ -434,7 +435,7 @@ public static function isContainedBy( } if ($container_type_part instanceof TTraitString - && (get_class($input_type_part) === TString::class + && ($input_type_part::class === TString::class || $input_type_part instanceof TNonEmptyString || $input_type_part instanceof TNonEmptyNonspecificLiteralString) ) { @@ -447,15 +448,15 @@ public static function isContainedBy( if (($input_type_part instanceof TClassString || $input_type_part instanceof TLiteralClassString) - && (get_class($container_type_part) === TSingleLetter::class - || get_class($container_type_part) === TNonEmptyString::class - || get_class($container_type_part) === TNonFalsyString::class) + && ($container_type_part::class === TSingleLetter::class + || $container_type_part::class === TNonEmptyString::class + || $container_type_part::class === TNonFalsyString::class) ) { return true; } if ($input_type_part instanceof TNumericString - && get_class($container_type_part) === TNonEmptyString::class + && $container_type_part::class === TNonEmptyString::class ) { return true; } @@ -514,7 +515,7 @@ public static function isContainedBy( } if ($input_type_part instanceof TLowercaseString - && get_class($container_type_part) === TNonEmptyString::class) { + && $container_type_part::class === TNonEmptyString::class) { return false; } diff --git a/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php b/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php index 3933881fd76..989cae51cb6 100644 --- a/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php +++ b/src/Psalm/Internal/Type/Comparator/TypeComparisonResult.php @@ -1,5 +1,7 @@ isVanillaMixed()) { return true; @@ -355,7 +357,7 @@ public static function isContainedBy( */ public static function isContainedByInPhp( ?Union $input_type, - Union $container_type + Union $container_type, ): bool { if ($container_type->isMixed()) { return true; @@ -403,7 +405,7 @@ public static function canBeContainedBy( Union $container_type, bool $ignore_null = false, bool $ignore_false = false, - array &$matching_input_keys = [] + array &$matching_input_keys = [], ): bool { if ($container_type->hasMixed()) { return true; @@ -455,7 +457,7 @@ public static function canExpressionTypesBeIdentical( Codebase $codebase, Union $type1, Union $type2, - bool $allow_interface_equality = true + bool $allow_interface_equality = true, ): bool { if ($type1->hasMixed() || $type2->hasMixed()) { return true; @@ -498,7 +500,7 @@ public static function canExpressionTypesBeIdentical( */ private static function getTypeParts( Codebase $codebase, - Union $union_type + Union $union_type, ): array { $atomic_types = []; foreach ($union_type->getAtomicTypes() as $atomic_type) { diff --git a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php index 88be51f72e1..b6e85303f01 100644 --- a/src/Psalm/Internal/Type/NegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/NegatedAssertionReconciler.php @@ -1,5 +1,7 @@ type_params[1], ], )); - } elseif ($assertion_type !== null && get_class($assertion_type) === TInt::class + } elseif ($assertion_type !== null && $assertion_type::class === TInt::class && isset($existing_var_type->getAtomicTypes()['array-key']) && !$is_equality ) { @@ -209,8 +209,7 @@ public static function reconcile( // fall through } elseif ($existing_var_type->isArray() && ($assertion->getAtomicType() instanceof TArray - || $assertion->getAtomicType() instanceof TKeyedArray - || $assertion->getAtomicType() instanceof TList) + || $assertion->getAtomicType() instanceof TKeyedArray) ) { //if both types are arrays, try to combine them $combined_type = TypeCombiner::combine( @@ -323,7 +322,7 @@ private static function handleLiteralNegatedEquality( ?string $key, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $existing_var_type = $existing_var_type->getBuilder(); $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); @@ -371,7 +370,7 @@ private static function handleLiteralNegatedEquality( } if (isset($existing_var_type->getAtomicTypes()['int']) - && get_class($existing_var_type->getAtomicTypes()['int']) === Type\Atomic\TInt::class + && $existing_var_type->getAtomicTypes()['int']::class === Type\Atomic\TInt::class ) { $redundant = false; //this may be used to generate a range containing any int except the one that was asserted against @@ -394,7 +393,7 @@ private static function handleLiteralNegatedEquality( } elseif ($assertion_type->value === "") { $existing_var_type->addType(new TNonEmptyString()); } - } elseif (get_class($assertion_type) === TLiteralString::class) { + } elseif ($assertion_type::class === TLiteralString::class) { $scalar_var_type = $assertion_type; } } elseif ($assertion_type instanceof TLiteralFloat) { @@ -414,7 +413,7 @@ private static function handleLiteralNegatedEquality( $case_name = $assertion_type->case_name; foreach ($existing_var_type->getAtomicTypes() as $atomic_key => $atomic_type) { - if (get_class($atomic_type) === TNamedObject::class + if ($atomic_type::class === TNamedObject::class && $atomic_type->value === $fq_enum_name ) { $codebase = $statements_analyzer->getCodebase(); diff --git a/src/Psalm/Internal/Type/ParseTree.php b/src/Psalm/Internal/Type/ParseTree.php index e181b07df84..d9dd689d29d 100644 --- a/src/Psalm/Internal/Type/ParseTree.php +++ b/src/Psalm/Internal/Type/ParseTree.php @@ -1,5 +1,7 @@ parent = $parent; } public function __destruct() diff --git a/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php b/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php index 8cd9cf7354b..ca1f6d004df 100644 --- a/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php +++ b/src/Psalm/Internal/Type/ParseTree/CallableParamTree.php @@ -1,5 +1,7 @@ value = $value; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php b/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php index a1dc3798253..3117db1adef 100644 --- a/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php +++ b/src/Psalm/Internal/Type/ParseTree/CallableWithReturnTypeTree.php @@ -1,5 +1,7 @@ condition = $condition; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php b/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php index 45027ce5ec3..3ff96485d33 100644 --- a/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php +++ b/src/Psalm/Internal/Type/ParseTree/EncapsulationTree.php @@ -1,5 +1,7 @@ value = $value; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php b/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php index fdaa1e98cfc..59043c04c4b 100644 --- a/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php +++ b/src/Psalm/Internal/Type/ParseTree/IndexedAccessTree.php @@ -1,5 +1,7 @@ value = $value; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php b/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php index f7bea8dbfb5..924dbea0b00 100644 --- a/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php +++ b/src/Psalm/Internal/Type/ParseTree/IntersectionTree.php @@ -1,5 +1,7 @@ value = $value; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php b/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php index f3ec3e4fe89..2008fb90300 100644 --- a/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php +++ b/src/Psalm/Internal/Type/ParseTree/KeyedArrayTree.php @@ -1,5 +1,7 @@ value = $value; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php b/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php index 39237a1b9d2..9a0a70937e1 100644 --- a/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php +++ b/src/Psalm/Internal/Type/ParseTree/MethodParamTree.php @@ -1,5 +1,7 @@ name = $name; - $this->byref = $byref; - $this->variadic = $variadic; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/MethodTree.php b/src/Psalm/Internal/Type/ParseTree/MethodTree.php index 12f800c565e..5e1da1ec6c8 100644 --- a/src/Psalm/Internal/Type/ParseTree/MethodTree.php +++ b/src/Psalm/Internal/Type/ParseTree/MethodTree.php @@ -1,5 +1,7 @@ value = $value; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php b/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php index ed81373f73b..81f2894d98a 100644 --- a/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php +++ b/src/Psalm/Internal/Type/ParseTree/MethodWithReturnTypeTree.php @@ -1,5 +1,7 @@ param_name = $param_name; - $this->as = $as; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php b/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php index 2cc6c06e3ac..07e29c9b149 100644 --- a/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php +++ b/src/Psalm/Internal/Type/ParseTree/TemplateIsTree.php @@ -1,5 +1,7 @@ param_name = $param_name; $this->parent = $parent; } } diff --git a/src/Psalm/Internal/Type/ParseTree/UnionTree.php b/src/Psalm/Internal/Type/ParseTree/UnionTree.php index 93f6195fcb5..18e4bac6054 100644 --- a/src/Psalm/Internal/Type/ParseTree/UnionTree.php +++ b/src/Psalm/Internal/Type/ParseTree/UnionTree.php @@ -1,5 +1,7 @@ offset_start = $offset_start; - $this->offset_end = $offset_end; - $this->value = $value; $this->parent = $parent; $this->text = $text === $value ? null : $text; } diff --git a/src/Psalm/Internal/Type/ParseTreeCreator.php b/src/Psalm/Internal/Type/ParseTreeCreator.php index 29e27602817..08779fd46a0 100644 --- a/src/Psalm/Internal/Type/ParseTreeCreator.php +++ b/src/Psalm/Internal/Type/ParseTreeCreator.php @@ -1,5 +1,7 @@ */ - private array $type_tokens; - - private int $type_token_count; + private readonly int $type_token_count; private int $t = 0; /** * @param list $type_tokens */ - public function __construct(array $type_tokens) + public function __construct(private array $type_tokens) { - $this->type_tokens = $type_tokens; $this->type_token_count = count($type_tokens); $this->parse_tree = new Root(); $this->current_leaf = $this->parse_tree; @@ -142,6 +141,7 @@ public function create(): ParseTree case 'is': case 'as': + case 'of': $this->handleIsOrAs($type_token); break; @@ -267,7 +267,7 @@ private function parseCallableParam(array $current_token, ParseTree $current_par $new_leaf->has_default = $has_default; $new_leaf->variadic = $variadic; $potential_name = substr($current_token[0], 1); - if ($potential_name !== false && $potential_name !== '') { + if ($potential_name !== '') { $new_leaf->name = $potential_name; } @@ -774,7 +774,7 @@ private function handleIsOrAs(array $type_token): void array_pop($current_parent->children); } - if ($type_token[0] === 'as') { + if ($type_token[0] === 'as' || $type_token[0] == 'of') { $next_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; if (!$this->current_leaf instanceof Value @@ -827,13 +827,34 @@ private function handleValue(array $type_token): void break; case '{': + ++$this->t; + + $nexter_token = $this->t + 1 < $this->type_token_count ? $this->type_tokens[$this->t + 1] : null; + + if ($nexter_token + && str_contains($nexter_token[0], '@') + && $type_token[0] !== 'list' + && $type_token[0] !== 'array' + ) { + $this->t = $this->type_token_count; + if ($type_token[0] === '$this') { + $type_token[0] = 'static'; + } + + $new_leaf = new Value( + $type_token[0], + $type_token[1], + $type_token[1] + strlen($type_token[0]), + $type_token[2] ?? null, + $new_parent, + ); + break; + } + $new_leaf = new KeyedArrayTree( $type_token[0], $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; diff --git a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php index ed40b070143..357dc8e6db2 100644 --- a/src/Psalm/Internal/Type/SimpleAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleAssertionReconciler.php @@ -1,5 +1,7 @@ getKeyedArray(); - } + if ($assertion_type instanceof TKeyedArray && $assertion_type->is_list @@ -430,7 +427,7 @@ public static function reconcile( ); } - if ($assertion_type && get_class($assertion_type) === TBool::class) { + if ($assertion_type && $assertion_type::class === TBool::class) { return self::reconcileBool( $assertion, $existing_var_type, @@ -469,7 +466,7 @@ public static function reconcile( ); } - if ($assertion_type && get_class($assertion_type) === TString::class) { + if ($assertion_type && $assertion_type::class === TString::class) { return self::reconcileString( $assertion, $existing_var_type, @@ -482,7 +479,7 @@ public static function reconcile( ); } - if ($assertion_type && get_class($assertion_type) === TInt::class) { + if ($assertion_type && $assertion_type::class === TInt::class) { return self::reconcileInt( $assertion, $existing_var_type, @@ -557,7 +554,7 @@ private static function reconcileIsset( array $suppressed_issues, int &$failed_reconciliation, bool $is_equality, - bool $inside_loop + bool $inside_loop, ): Union { $existing_var_type = $existing_var_type->getBuilder(); $old_var_type_string = $existing_var_type->getId(); @@ -624,7 +621,7 @@ private static function reconcileNonEmptyCountable( bool $negated, ?CodeLocation $code_location, array $suppressed_issues, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $existing_var_type = $existing_var_type->getBuilder(); @@ -759,7 +756,7 @@ private static function reconcileExactlyCountable( bool $negated, ?CodeLocation $code_location, array $suppressed_issues, - bool $is_equality + bool $is_equality, ): Union { $existing_var_type = $existing_var_type->getBuilder(); if ($existing_var_type->hasType('array')) { @@ -880,7 +877,7 @@ private static function reconcileHasMethod( bool $negated, ?CodeLocation $code_location, array $suppressed_issues, - int &$failed_reconciliation + int &$failed_reconciliation, ): Union { $method_name = $assertion->method; $old_var_type_string = $existing_var_type->getId(); @@ -991,7 +988,7 @@ private static function reconcileString( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $existing_var_atomic_types = $existing_var_type->getAtomicTypes(); @@ -1009,7 +1006,7 @@ private static function reconcileString( foreach ($existing_var_atomic_types as $type) { if ($type instanceof TString) { - if (get_class($type) === TString::class) { + if ($type::class === TString::class) { $type = $type->setFromDocblock(false); } $string_types[] = $type; @@ -1083,7 +1080,7 @@ private static function reconcileInt( bool $negated, ?CodeLocation $code_location, array $suppressed_issues, - int &$failed_reconciliation + int &$failed_reconciliation, ): Union { if ($existing_var_type->hasMixed()) { if ($assertion instanceof IsLooselyEqual) { @@ -1101,7 +1098,7 @@ private static function reconcileInt( foreach ($existing_var_atomic_types as $type) { if ($type instanceof TInt) { - if (get_class($type) === TInt::class) { + if ($type::class === TInt::class) { $type = $type->setFromDocblock(false); } @@ -1176,7 +1173,7 @@ private static function reconcileBool( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return Type::getBool(); @@ -1253,7 +1250,7 @@ private static function reconcileFalse( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return Type::getFalse(); @@ -1336,7 +1333,7 @@ private static function reconcileTrue( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return Type::getTrue(); @@ -1419,7 +1416,7 @@ private static function reconcileScalar( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return Type::getScalar(); @@ -1492,7 +1489,7 @@ private static function reconcileNumeric( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return Type::getNumeric(); @@ -1585,7 +1582,7 @@ private static function reconcileObject( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return new Union([$assertion_type]); @@ -1701,7 +1698,7 @@ private static function reconcileResource( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return Type::getResource(); @@ -1758,7 +1755,7 @@ private static function reconcileCountable( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); @@ -1828,7 +1825,7 @@ private static function reconcileIterable( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); @@ -1888,7 +1885,7 @@ private static function reconcileInArray( bool $negated, ?CodeLocation $code_location, array $suppressed_issues, - int &$failed_reconciliation + int &$failed_reconciliation, ): Union { $new_var_type = $assertion->type; @@ -1923,14 +1920,11 @@ private static function reconcileInArray( private static function reconcileHasArrayKey( Union $existing_var_type, - HasArrayKey $assertion + HasArrayKey $assertion, ): Union { $assertion = $assertion->key; $types = $existing_var_type->getAtomicTypes(); foreach ($types as &$atomic_type) { - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } if ($atomic_type instanceof TKeyedArray) { assert(strpos($assertion, '::class') === (strlen($assertion)-7)); [$assertion] = explode('::', $assertion); @@ -1964,7 +1958,7 @@ private static function reconcileIsGreaterThan( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $existing_var_type = $existing_var_type->getBuilder(); //we add 1 from the assertion value because we're on a strict operator @@ -2073,7 +2067,7 @@ private static function reconcileIsLessThan( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { //we remove 1 from the assertion value because we're on a strict operator $assertion_value = $assertion->value - 1; @@ -2181,7 +2175,7 @@ private static function reconcileTraversable( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); @@ -2249,7 +2243,7 @@ private static function reconcileArray( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); @@ -2268,9 +2262,6 @@ private static function reconcileArray( $redundant = true; foreach ($existing_var_atomic_types as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type instanceof TArray) { if ($atomic_assertion_type instanceof TNonEmptyArray) { $array_types[] = new TNonEmptyArray( @@ -2290,7 +2281,7 @@ private static function reconcileArray( } elseif ($type instanceof TCallable) { $array_types[] = new TCallableKeyedArray([ new Union([new TClassString, new TObject]), - Type::getString(), + Type::getNonEmptyString(), ]); $redundant = false; @@ -2363,7 +2354,7 @@ private static function reconcileList( array $suppressed_issues, int &$failed_reconciliation, bool $is_equality, - bool $is_non_empty + bool $is_non_empty, ): Union { $old_var_type_string = $existing_var_type->getId(); @@ -2377,9 +2368,6 @@ private static function reconcileList( $redundant = true; foreach ($existing_var_atomic_types as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type instanceof TKeyedArray && $type->is_list) { if ($is_non_empty && !$type->isNonEmpty()) { $properties = $type->properties; @@ -2415,7 +2403,7 @@ private static function reconcileList( } elseif ($type instanceof TCallable) { $array_types[] = new TCallableKeyedArray([ new Union([new TClassString, new TObject]), - Type::getString(), + Type::getNonEmptyString(), ]); $redundant = false; @@ -2471,7 +2459,7 @@ private static function reconcileStringArrayAccess( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $inside_loop + bool $inside_loop, ): Union { $old_var_type_string = $existing_var_type->getId(); @@ -2487,11 +2475,8 @@ private static function reconcileStringArrayAccess( $array_types = []; foreach ($existing_var_atomic_types as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type->isArrayAccessibleWithStringKey($codebase)) { - if (get_class($type) === TArray::class) { + if ($type::class === TArray::class) { $array_types[] = new TNonEmptyArray($type->type_params); } elseif ($type instanceof TKeyedArray && $type->is_list) { $properties = $type->properties; @@ -2542,7 +2527,7 @@ private static function reconcileIntArrayAccess( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $inside_loop + bool $inside_loop, ): Union { $old_var_type_string = $existing_var_type->getId(); @@ -2556,7 +2541,7 @@ private static function reconcileIntArrayAccess( foreach ($existing_var_atomic_types as $type) { if ($type->isArrayAccessibleWithIntOrStringKey($codebase)) { - if (get_class($type) === TArray::class) { + if ($type::class === TArray::class) { $array_types[] = new TNonEmptyArray($type->type_params); } else { $array_types[] = $type; @@ -2603,7 +2588,7 @@ private static function reconcileCallable( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { if ($existing_var_type->hasMixed()) { return Type::parseString('callable'); @@ -2617,9 +2602,6 @@ private static function reconcileCallable( $redundant = true; foreach ($existing_var_atomic_types as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type->isCallableType()) { $callable_types[] = $type; } elseif ($type instanceof TObject) { @@ -2630,19 +2612,19 @@ private static function reconcileCallable( && $codebase->methodExists($type->value . '::__invoke') ) { $callable_types[] = $type; - } elseif (get_class($type) === TString::class - || get_class($type) === TNonEmptyString::class - || get_class($type) === TNonFalsyString::class + } elseif ($type::class === TString::class + || $type::class === TNonEmptyString::class + || $type::class === TNonFalsyString::class ) { $callable_types[] = new TCallableString(); $redundant = false; - } elseif (get_class($type) === TLiteralString::class + } elseif ($type::class === TLiteralString::class && InternalCallMapHandler::inCallMap($type->value) ) { $callable_types[] = $type; $redundant = false; } elseif ($type instanceof TArray) { - $type = new TCallableArray($type->type_params); + $type = new TCallableKeyedArray($type->type_params); $callable_types[] = $type; $redundant = false; } elseif ($type instanceof TKeyedArray && count($type->properties) === 2) { @@ -2716,7 +2698,7 @@ private static function reconcileTruthyOrNonEmpty( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $recursive_check + bool $recursive_check, ): Union { $types = $existing_var_type->getAtomicTypes(); $old_var_type_string = $existing_var_type->getId(); @@ -2790,9 +2772,7 @@ private static function reconcileTruthyOrNonEmpty( if (isset($types['array'])) { $array_atomic_type = $types['array']; - if ($array_atomic_type instanceof TList) { - $array_atomic_type = $array_atomic_type->getKeyedArray(); - } + if ($array_atomic_type instanceof TArray && !$array_atomic_type instanceof TNonEmptyArray @@ -2813,7 +2793,7 @@ private static function reconcileTruthyOrNonEmpty( if (isset($types['mixed'])) { $mixed_atomic_type = $types['mixed']; - if (get_class($mixed_atomic_type) === TMixed::class) { + if ($mixed_atomic_type::class === TMixed::class) { unset($types['mixed']); $types []= new TNonEmptyMixed(); } @@ -2822,7 +2802,7 @@ private static function reconcileTruthyOrNonEmpty( if (isset($types['scalar'])) { $scalar_atomic_type = $types['scalar']; - if (get_class($scalar_atomic_type) === TScalar::class) { + if ($scalar_atomic_type::class === TScalar::class) { unset($types['scalar']); $types []= new TNonEmptyScalar(); } @@ -2831,16 +2811,16 @@ private static function reconcileTruthyOrNonEmpty( if (isset($types['string'])) { $string_atomic_type = $types['string']; - if (get_class($string_atomic_type) === TString::class) { + if ($string_atomic_type::class === TString::class) { unset($types['string']); $types []= new TNonFalsyString(); - } elseif (get_class($string_atomic_type) === TLowercaseString::class) { + } elseif ($string_atomic_type::class === TLowercaseString::class) { unset($types['string']); $types []= new TNonEmptyLowercaseString(); - } elseif (get_class($string_atomic_type) === TNonspecificLiteralString::class) { + } elseif ($string_atomic_type::class === TNonspecificLiteralString::class) { unset($types['string']); $types []= new TNonEmptyNonspecificLiteralString(); - } elseif (get_class($string_atomic_type) === TNonEmptyString::class) { + } elseif ($string_atomic_type::class === TNonEmptyString::class) { unset($types['string']); $types []= new TNonFalsyString(); } @@ -2910,7 +2890,7 @@ private static function reconcileClassConstant( Codebase $codebase, TClassConstant $class_constant_expression, Union $existing_type, - int &$failed_reconciliation + int &$failed_reconciliation, ): Union { $class_name = $class_constant_expression->fq_classlike_name; if (!$codebase->classlike_storage_provider->has($class_name)) { @@ -2939,7 +2919,7 @@ private static function reconcileClassConstant( private static function reconcileValueOf( Codebase $codebase, TValueOf $assertion_type, - int &$failed_reconciliation + int &$failed_reconciliation, ): ?Union { $reconciled_types = []; @@ -2976,7 +2956,7 @@ private static function reconcileValueOf( $enum_value !== null, 'Verified enum type above, value can not contain `null` anymore.', ); - $reconciled_types[] = Type::getLiteral($enum_value); + $reconciled_types[] = $enum_value; } continue; @@ -2988,9 +2968,8 @@ private static function reconcileValueOf( } $enum_value = $enum_case->getValue($codebase->classlikes); - assert($enum_value !== null, 'Verified enum type above, value can not contain `null` anymore.'); - $reconciled_types[] = Type::getLiteral($enum_value); + $reconciled_types[] = $enum_value; } if ($reconciled_types === []) { diff --git a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php index 81e4208d7df..4770a8b18f3 100644 --- a/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php +++ b/src/Psalm/Internal/Type/SimpleNegatedAssertionReconciler.php @@ -1,5 +1,7 @@ getId(); @@ -96,7 +97,7 @@ public static function reconcile( if (!$existing_var_type->isNullable() && $key - && strpos($key, '[') === false + && !str_contains($key, '[') && (!$existing_var_type->hasMixed() || $existing_var_type->isAlwaysTruthy()) ) { if ($code_location) { @@ -291,7 +292,7 @@ public static function reconcile( ); } - if ($assertion_type && get_class($assertion_type) === TBool::class && !$existing_var_type->hasMixed()) { + if ($assertion_type && $assertion_type::class === TBool::class && !$existing_var_type->hasMixed()) { return self::reconcileBool( $assertion, $existing_var_type, @@ -330,7 +331,7 @@ public static function reconcile( ); } - if ($assertion_type && get_class($assertion_type) === TInt::class && !$existing_var_type->hasMixed()) { + if ($assertion_type && $assertion_type::class === TInt::class && !$existing_var_type->hasMixed()) { return self::reconcileInt( $assertion, $existing_var_type, @@ -343,7 +344,7 @@ public static function reconcile( ); } - if ($assertion_type && get_class($assertion_type) === TString::class && !$existing_var_type->hasMixed()) { + if ($assertion_type && $assertion_type::class === TString::class && !$existing_var_type->hasMixed()) { return self::reconcileString( $assertion, $existing_var_type, @@ -426,7 +427,7 @@ public static function reconcile( private static function reconcileCallable( Union $existing_var_type, Codebase $codebase, - TCallable $assertion_type + TCallable $assertion_type, ): Union { $existing_var_type = $existing_var_type->getBuilder(); foreach ($existing_var_type->getAtomicTypes() as $atomic_key => $type) { @@ -468,7 +469,7 @@ private static function reconcileBool( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_bool_types = []; @@ -500,7 +501,7 @@ private static function reconcileBool( $non_bool_types[] = $type; } } elseif (!$type instanceof TBool - || ($is_equality && get_class($type) === TBool::class) + || ($is_equality && $type::class === TBool::class) ) { if ($type instanceof TScalar) { $redundant = false; @@ -554,7 +555,7 @@ private static function reconcileNotNonEmptyCountable( ?CodeLocation $code_location, array $suppressed_issues, bool $is_equality, - ?int $count + ?int $count, ): Union { $existing_var_type = $existing_var_type->getBuilder(); $old_var_type_string = $existing_var_type->getId(); @@ -674,7 +675,7 @@ private static function reconcileNull( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $types = $existing_var_type->getAtomicTypes(); $old_var_type_string = $existing_var_type->getId(); @@ -749,7 +750,7 @@ private static function reconcileFalse( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $types = $existing_var_type->getAtomicTypes(); $old_var_type_string = $existing_var_type->getId(); @@ -829,7 +830,7 @@ private static function reconcileTrue( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $types = $existing_var_type->getAtomicTypes(); $old_var_type_string = $existing_var_type->getId(); @@ -910,7 +911,7 @@ private static function reconcileFalsyOrEmpty( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $recursive_check + bool $recursive_check, ): Union { $existing_var_type = $existing_var_type->getBuilder(); $old_var_type_string = $existing_var_type->getId(); @@ -990,7 +991,7 @@ private static function reconcileFalsyOrEmpty( if ($existing_var_type->hasMixed()) { $mixed_atomic_type = $existing_var_type->getAtomicTypes()['mixed']; - if (get_class($mixed_atomic_type) === TMixed::class) { + if ($mixed_atomic_type::class === TMixed::class) { $existing_var_type->removeType('mixed'); $existing_var_type->addType(new TEmptyMixed()); } @@ -999,7 +1000,7 @@ private static function reconcileFalsyOrEmpty( if ($existing_var_type->hasScalar()) { $scalar_atomic_type = $existing_var_type->getAtomicTypes()['scalar']; - if (get_class($scalar_atomic_type) === TScalar::class) { + if ($scalar_atomic_type::class === TScalar::class) { $existing_var_type->removeType('scalar'); $existing_var_type->addType(new TEmptyScalar()); } @@ -1008,17 +1009,17 @@ private static function reconcileFalsyOrEmpty( if ($existing_var_type->hasType('string')) { $string_atomic_type = $existing_var_type->getAtomicTypes()['string']; - if (get_class($string_atomic_type) === TString::class) { + if ($string_atomic_type::class === TString::class) { $existing_var_type->removeType('string'); $existing_var_type->addType(Type::getAtomicStringFromLiteral('')); $existing_var_type->addType(Type::getAtomicStringFromLiteral('0')); - } elseif (get_class($string_atomic_type) === TNonEmptyString::class) { + } elseif ($string_atomic_type::class === TNonEmptyString::class) { $existing_var_type->removeType('string'); $existing_var_type->addType(Type::getAtomicStringFromLiteral('0')); - } elseif (get_class($string_atomic_type) === TNonEmptyLowercaseString::class) { + } elseif ($string_atomic_type::class === TNonEmptyLowercaseString::class) { $existing_var_type->removeType('string'); $existing_var_type->addType(Type::getAtomicStringFromLiteral('0')); - } elseif (get_class($string_atomic_type) === TNonEmptyNonspecificLiteralString::class) { + } elseif ($string_atomic_type::class === TNonEmptyNonspecificLiteralString::class) { $existing_var_type->removeType('string'); $existing_var_type->addType(Type::getAtomicStringFromLiteral('0')); } @@ -1091,7 +1092,7 @@ private static function reconcileScalar( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_scalar_types = []; @@ -1177,7 +1178,7 @@ private static function reconcileObject( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_object_types = []; @@ -1209,9 +1210,9 @@ private static function reconcileObject( $non_object_types[] = $type; } } elseif ($type instanceof TCallable) { - $non_object_types[] = new TCallableArray([ - Type::getArrayKey(), - Type::getMixed(), + $non_object_types[] = new TCallableKeyedArray([ + new Union([new TClassString, new TObject]), + Type::getNonEmptyString(), ]); $non_object_types[] = new TCallableString(); $redundant = false; @@ -1276,7 +1277,7 @@ private static function reconcileNumeric( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_numeric_types = []; @@ -1370,7 +1371,7 @@ private static function reconcileInt( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_int_types = []; @@ -1470,7 +1471,7 @@ private static function reconcileFloat( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_float_types = []; @@ -1565,7 +1566,7 @@ private static function reconcileString( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_string_types = []; @@ -1600,9 +1601,9 @@ private static function reconcileString( $non_string_types[] = new TInt(); $redundant = false; } elseif ($type instanceof TCallable) { - $non_string_types[] = new TCallableArray([ - Type::getArrayKey(), - Type::getMixed(), + $non_string_types[] = new TCallableKeyedArray([ + new Union([new TClassString, new TObject]), + Type::getNonEmptyString(), ]); $non_string_types[] = new TCallableObject(); $redundant = false; @@ -1669,16 +1670,13 @@ private static function reconcileArray( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $old_var_type_string = $existing_var_type->getId(); $non_array_types = []; $redundant = !$existing_var_type->hasScalar(); foreach ($existing_var_type->getAtomicTypes() as $type) { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type instanceof TTemplateParam) { if (!$is_equality && !$type->as->isMixed()) { $template_did_fail = 0; @@ -1772,7 +1770,7 @@ private static function reconcileResource( ?CodeLocation $code_location, array $suppressed_issues, int &$failed_reconciliation, - bool $is_equality + bool $is_equality, ): Union { $types = $existing_var_type->getAtomicTypes(); $old_var_type_string = $existing_var_type->getId(); @@ -1841,7 +1839,7 @@ private static function reconcileIsLessThanOrEqualTo( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $existing_var_type = $existing_var_type->getBuilder(); $assertion_value = $assertion->value; @@ -1948,7 +1946,7 @@ private static function reconcileIsGreaterThanOrEqualTo( ?string $var_id, bool $negated, ?CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): Union { $existing_var_type = $existing_var_type->getBuilder(); $assertion_value = $assertion->value; diff --git a/src/Psalm/Internal/Type/TemplateBound.php b/src/Psalm/Internal/Type/TemplateBound.php index 71e219afa43..1e827651ebc 100644 --- a/src/Psalm/Internal/Type/TemplateBound.php +++ b/src/Psalm/Internal/Type/TemplateBound.php @@ -1,5 +1,7 @@ >> the type T appears at three different depths. - * - * The shallowest-appearance of the template takes prominence when inferring the type of T. - */ - public int $appearance_depth; - - /** - * The argument offset where this template was set - * - * In the type Foo the type appears at argument offsets 0 and 2 - */ - public ?int $arg_offset = null; - - /** - * When non-null, indicates an equality template bound (vs a lower or upper bound) - */ - public ?string $equality_bound_classlike = null; - public function __construct( - Union $type, - int $appearance_depth = 0, - ?int $arg_offset = null, - ?string $equality_bound_classlike = null + public Union $type, + /** + * This is the depth at which the template appears in a given type. + * + * In the type Foo>> the type T appears at three different depths. + * + * The shallowest-appearance of the template takes prominence when inferring the type of T. + */ + public int $appearance_depth = 0, + /** + * The argument offset where this template was set + * + * In the type Foo the type appears at argument offsets 0 and 2 + */ + public ?int $arg_offset = null, + /** + * When non-null, indicates an equality template bound (vs a lower or upper bound) + */ + public ?string $equality_bound_classlike = null, ) { - $this->type = $type; - $this->appearance_depth = $appearance_depth; - $this->arg_offset = $arg_offset; - $this->equality_bound_classlike = $equality_bound_classlike; } } diff --git a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php index ab367ca66c8..4f3c192e919 100644 --- a/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateInferredTypeReplacer.php @@ -1,5 +1,7 @@ extra_types); + assert($first_atomic_type !== null); if ($atomic_type->extra_types) { $first_atomic_type = $first_atomic_type->setIntersectionTypes($atomic_type->extra_types); @@ -294,7 +298,7 @@ private static function replaceTemplateParam( } elseif ($codebase) { foreach ($inferred_lower_bounds as $template_type_map) { foreach ($template_type_map as $template_class => $_) { - if (strpos($template_class, 'fn-') === 0) { + if (str_starts_with($template_class, 'fn-')) { continue; } @@ -319,7 +323,7 @@ private static function replaceTemplateParam( } } } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { } } } @@ -335,7 +339,7 @@ private static function replaceTemplateParam( private static function replaceTemplateKeyOfValueOf( ?Codebase $codebase, Atomic $atomic_type, - array $inferred_lower_bounds + array $inferred_lower_bounds, ): ?Atomic { if (!isset($inferred_lower_bounds[$atomic_type->param_name][$atomic_type->defining_class])) { return null; @@ -367,7 +371,7 @@ private static function replaceTemplateKeyOfValueOf( private static function replaceTemplatePropertiesOf( ?Codebase $codebase, TTemplatePropertiesOf $atomic_type, - array $inferred_lower_bounds + array $inferred_lower_bounds, ): ?Atomic { if (!isset($inferred_lower_bounds[$atomic_type->param_name][$atomic_type->defining_class])) { return null; @@ -396,7 +400,7 @@ private static function replaceConditional( TemplateResult $template_result, Codebase $codebase, TConditional &$atomic_type, - array $inferred_lower_bounds + array $inferred_lower_bounds, ): Union { $template_type = isset($inferred_lower_bounds[$atomic_type->param_name][$atomic_type->defining_class]) ? TemplateStandinTypeReplacer::getMostSpecificTypeFromBounds( diff --git a/src/Psalm/Internal/Type/TemplateResult.php b/src/Psalm/Internal/Type/TemplateResult.php index a759e4f5507..31663158288 100644 --- a/src/Psalm/Internal/Type/TemplateResult.php +++ b/src/Psalm/Internal/Type/TemplateResult.php @@ -1,10 +1,11 @@ > - */ - public array $template_types; - /** * @var array>> */ - public array $lower_bounds; + public array $lower_bounds = []; /** * @var array> @@ -55,11 +51,8 @@ final class TemplateResult * @param array> $template_types * @param array> $lower_bounds */ - public function __construct(array $template_types, array $lower_bounds) + public function __construct(public array $template_types, array $lower_bounds) { - $this->template_types = $template_types; - $this->lower_bounds = []; - foreach ($lower_bounds as $key1 => $boundSet) { foreach ($boundSet as $key2 => $bound) { $this->lower_bounds[$key1][$key2] = [new TemplateBound($bound)]; @@ -77,7 +70,7 @@ public function merge(TemplateResult $result): TemplateResult /** @var array>> $lower_bounds */ $lower_bounds = array_replace_recursive($instance->lower_bounds, $result->lower_bounds); $instance->lower_bounds = $lower_bounds; - $instance->template_types = array_merge($instance->template_types, $result->template_types); + $instance->template_types = [...$instance->template_types, ...$result->template_types]; return $instance; } diff --git a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php index 7384d3bce88..07baa497a82 100644 --- a/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php +++ b/src/Psalm/Internal/Type/TemplateStandinTypeReplacer.php @@ -1,5 +1,7 @@ getSingleAtomic(); $offset_template_type = $offset_template_type->getSingleAtomic(); - if ($array_template_type instanceof TList) { - $array_template_type = $array_template_type->getKeyedArray(); - } + if ($array_template_type instanceof TKeyedArray && ($offset_template_type instanceof TLiteralString || $offset_template_type instanceof TLiteralInt) @@ -345,9 +345,6 @@ private static function handleAtomicStandin( if ($template_type) { foreach ($template_type->getAtomicTypes() as $template_atomic) { - if ($template_atomic instanceof TList) { - $template_atomic = $template_atomic->getKeyedArray(); - } if (!$template_atomic instanceof TKeyedArray && !$template_atomic instanceof TArray ) { @@ -470,15 +467,11 @@ private static function findMatchingAtomicTypesForTemplate( string $key, Codebase $codebase, ?StatementsAnalyzer $statements_analyzer, - Union $input_type + Union $input_type, ): array { $matching_atomic_types = []; foreach ($input_type->getAtomicTypes() as $input_key => $atomic_input_type) { - if ($atomic_input_type instanceof TList) { - $atomic_input_type = $atomic_input_type->getKeyedArray(); - } - if ($bracket_pos = strpos($input_key, '<')) { $input_key = substr($input_key, 0, $bracket_pos); } @@ -513,7 +506,7 @@ private static function findMatchingAtomicTypesForTemplate( continue; } - if (strpos($input_key, $key . '&') === 0) { + if (str_starts_with($input_key, $key . '&')) { $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; continue; } @@ -538,7 +531,7 @@ private static function findMatchingAtomicTypesForTemplate( $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; continue; } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // do nothing } } @@ -600,7 +593,7 @@ private static function findMatchingAtomicTypesForTemplate( $matching_atomic_types[$atomic_input_type->getId()] = $atomic_input_type; continue; } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // do nothing } } @@ -656,7 +649,7 @@ private static function handleTemplateParamStandin( bool $add_lower_bound, ?string $bound_equality_classlike, int $depth, - bool &$had_template + bool &$had_template, ): array { if ($atomic_type->defining_class === $calling_class) { return [$atomic_type]; @@ -760,9 +753,6 @@ private static function handleTemplateParamStandin( if ($keyed_template->isSingle()) { $keyed_template = $keyed_template->getSingleAtomic(); } - if ($keyed_template instanceof \Psalm\Type\Atomic\TList) { - $keyed_template = $keyed_template->getKeyedArray(); - } if ($keyed_template instanceof TKeyedArray || $keyed_template instanceof TArray @@ -1008,7 +998,7 @@ public static function handleTemplateParamClassStandin( bool $add_lower_bound, ?string $bound_equality_classlike, int $depth, - bool $was_single + bool $was_single, ): array { if ($atomic_type->defining_class === $calling_class) { return [$atomic_type]; @@ -1153,7 +1143,7 @@ public static function getRootTemplateType( string $param_name, string $defining_class, array $visited_classes, - ?Codebase $codebase + ?Codebase $codebase, ): ?Union { if (isset($visited_classes[$defining_class])) { return null; @@ -1252,7 +1242,7 @@ public static function getMappedGenericTypeParams( Codebase $codebase, Atomic $input_type_part, Atomic $container_type_part, - ?array &$container_type_params_covariant = null + ?array &$container_type_params_covariant = null, ): array { if ($input_type_part instanceof TGenericObject || $input_type_part instanceof TIterable) { $input_type_params = $input_type_part->type_params; diff --git a/src/Psalm/Internal/Type/TypeAlias.php b/src/Psalm/Internal/Type/TypeAlias.php index a8c05fb6d30..3e5da8b5069 100644 --- a/src/Psalm/Internal/Type/TypeAlias.php +++ b/src/Psalm/Internal/Type/TypeAlias.php @@ -1,5 +1,7 @@ - */ - public array $replacement_atomic_types; - /** * @param list $replacement_atomic_types */ - public function __construct(array $replacement_atomic_types) + public function __construct(public array $replacement_atomic_types) { - $this->replacement_atomic_types = $replacement_atomic_types; } } diff --git a/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php b/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php index 03d09f216fb..4a6bd27037c 100644 --- a/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php +++ b/src/Psalm/Internal/Type/TypeAlias/InlineTypeAlias.php @@ -1,5 +1,7 @@ - */ - public array $replacement_tokens; - /** * @param list $replacement_tokens */ - public function __construct(array $replacement_tokens) + public function __construct(public readonly array $replacement_tokens) { - $this->replacement_tokens = $replacement_tokens; } } diff --git a/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php b/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php index 3eb75ce824c..e3a35026991 100644 --- a/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php +++ b/src/Psalm/Internal/Type/TypeAlias/LinkableTypeAlias.php @@ -1,5 +1,7 @@ declaring_fq_classlike_name = $declaring_fq_classlike_name; - $this->alias_name = $alias_name; - $this->line_number = $line_number; - $this->start_offset = $start_offset; - $this->end_offset = $end_offset; } } diff --git a/src/Psalm/Internal/Type/TypeCombination.php b/src/Psalm/Internal/Type/TypeCombination.php index 94e64793e8b..c80c79acc51 100644 --- a/src/Psalm/Internal/Type/TypeCombination.php +++ b/src/Psalm/Internal/Type/TypeCombination.php @@ -1,5 +1,7 @@ value_types['string']) - && get_class($combination->value_types['string']) === TString::class; + && $combination->value_types['string']::class === TString::class; if (!$has_non_specific_string) { $object_type = self::combine( @@ -399,11 +398,8 @@ private static function scrapeTypeProperties( ?Codebase $codebase, bool $overwrite_empty_array, bool $allow_mixed_union, - int $literal_limit + int $literal_limit, ): ?Union { - if ($type instanceof TList) { - $type = $type->getKeyedArray(); - } if ($type instanceof TMixed) { if ($type->from_loop_isset) { if ($combination->mixed_from_loop_isset === null) { @@ -442,11 +438,11 @@ private static function scrapeTypeProperties( return null; } - if (get_class($type) === TBool::class && isset($combination->value_types['false'])) { + if ($type::class === TBool::class && isset($combination->value_types['false'])) { unset($combination->value_types['false']); } - if (get_class($type) === TBool::class && isset($combination->value_types['true'])) { + if ($type::class === TBool::class && isset($combination->value_types['true'])) { unset($combination->value_types['true']); } @@ -547,11 +543,17 @@ private static function scrapeTypeProperties( } } - if ($type instanceof TArray && $type_key === 'array') { - if ($type instanceof TCallableArray && isset($combination->value_types['callable'])) { + if ($type instanceof TCallableKeyedArray) { + if (isset($combination->value_types['callable'])) { return null; } - + if ($combination->all_arrays_callable !== false) { + $combination->all_arrays_callable = true; + } else { + $combination->all_arrays_callable = false; + } + } + if ($type instanceof TArray && $type_key === 'array') { foreach ($type->type_params as $i => $type_param) { // See https://github.com/vimeo/psalm/pull/9439#issuecomment-1464563015 /** @psalm-suppress PropertyTypeCoercion */ @@ -590,14 +592,7 @@ private static function scrapeTypeProperties( $combination->all_arrays_class_string_maps = false; } - if ($type instanceof TCallableArray) { - if ($combination->all_arrays_callable !== false) { - $combination->all_arrays_callable = true; - } - } else { - $combination->all_arrays_callable = false; - } - + $combination->all_arrays_callable = false; return null; } @@ -973,8 +968,8 @@ private static function scrapeTypeProperties( if ($type instanceof TCallable && $type_key === 'callable') { if (($combination->value_types['string'] ?? null) instanceof TCallableString) { unset($combination->value_types['string']); - } elseif (!empty($combination->array_type_params) && $combination->all_arrays_callable) { - $combination->array_type_params = []; + } elseif (!empty($combination->objectlike_entries) && $combination->all_arrays_callable) { + $combination->objectlike_entries = []; } elseif (isset($combination->value_types['callable-object'])) { unset($combination->value_types['callable-object']); } @@ -989,7 +984,7 @@ private static function scrapeStringProperties( Atomic $type, TypeCombination $combination, ?Codebase $codebase, - int $literal_limit + int $literal_limit, ): void { if ($type instanceof TCallableString && isset($combination->value_types['callable'])) { return; @@ -1168,7 +1163,7 @@ private static function scrapeStringProperties( if ($has_empty_string) { $combination->value_types['string'] = new TString(); - } elseif ($has_non_lowercase_string && get_class($type) !== TNonEmptyString::class) { + } elseif ($has_non_lowercase_string && $type::class !== TNonEmptyString::class) { $combination->value_types['string'] = new TNonEmptyString(); } else { $combination->value_types['string'] = $type; @@ -1181,60 +1176,60 @@ private static function scrapeStringProperties( } else { $combination->value_types[$type_key] = $type; } - } elseif (get_class($combination->value_types['string']) !== TString::class) { - if (get_class($type) === TString::class) { + } elseif ($combination->value_types['string']::class !== TString::class) { + if ($type::class === TString::class) { $combination->value_types['string'] = $type; - } elseif (get_class($combination->value_types['string']) !== get_class($type)) { - if (get_class($type) === TNonEmptyString::class - && get_class($combination->value_types['string']) === TNumericString::class + } elseif ($combination->value_types['string']::class !== $type::class) { + if ($type::class === TNonEmptyString::class + && $combination->value_types['string']::class === TNumericString::class ) { $combination->value_types['string'] = $type; - } elseif (get_class($type) === TNumericString::class - && get_class($combination->value_types['string']) === TNonEmptyString::class + } elseif ($type::class === TNumericString::class + && $combination->value_types['string']::class === TNonEmptyString::class ) { // do nothing - } elseif ((get_class($type) === TNonEmptyString::class - || get_class($type) === TNumericString::class) - && get_class($combination->value_types['string']) === TNonFalsyString::class + } elseif (($type::class === TNonEmptyString::class + || $type::class === TNumericString::class) + && $combination->value_types['string']::class === TNonFalsyString::class ) { $combination->value_types['string'] = $type; - } elseif (get_class($type) === TNonFalsyString::class - && (get_class($combination->value_types['string']) === TNonEmptyString::class - || get_class($combination->value_types['string']) === TNumericString::class) + } elseif ($type::class === TNonFalsyString::class + && ($combination->value_types['string']::class === TNonEmptyString::class + || $combination->value_types['string']::class === TNumericString::class) ) { // do nothing - } elseif ((get_class($type) === TNonEmptyString::class - || get_class($type) === TNonFalsyString::class) - && get_class($combination->value_types['string']) === TNonEmptyLowercaseString::class + } elseif (($type::class === TNonEmptyString::class + || $type::class === TNonFalsyString::class) + && $combination->value_types['string']::class === TNonEmptyLowercaseString::class ) { $combination->value_types['string'] = new TNonEmptyString(); - } elseif ((get_class($combination->value_types['string']) === TNonEmptyString::class - || get_class($combination->value_types['string']) === TNonFalsyString::class) - && get_class($type) === TNonEmptyLowercaseString::class + } elseif (($combination->value_types['string']::class === TNonEmptyString::class + || $combination->value_types['string']::class === TNonFalsyString::class) + && $type::class === TNonEmptyLowercaseString::class ) { $combination->value_types['string'] = new TNonEmptyString(); - } elseif (get_class($type) === TLowercaseString::class - && get_class($combination->value_types['string']) === TNonEmptyLowercaseString::class + } elseif ($type::class === TLowercaseString::class + && $combination->value_types['string']::class === TNonEmptyLowercaseString::class ) { $combination->value_types['string'] = $type; - } elseif (get_class($combination->value_types['string']) === TLowercaseString::class - && get_class($type) === TNonEmptyLowercaseString::class + } elseif ($combination->value_types['string']::class === TLowercaseString::class + && $type::class === TNonEmptyLowercaseString::class ) { //no-change - } elseif (get_class($combination->value_types['string']) + } elseif ($combination->value_types['string']::class === TNonEmptyNonspecificLiteralString::class && $type instanceof TNonEmptyString ) { $combination->value_types['string'] = new TNonEmptyString(); - } elseif (get_class($type) === TNonEmptyNonspecificLiteralString::class + } elseif ($type::class === TNonEmptyNonspecificLiteralString::class && ( $combination->value_types['string'] instanceof TNonEmptyString || $combination->value_types['string'] instanceof TNonspecificLiteralString ) ) { // do nothing - } elseif (get_class($type) === TNonspecificLiteralString::class - && get_class($combination->value_types['string']) === TNonEmptyNonspecificLiteralString::class + } elseif ($type::class === TNonspecificLiteralString::class + && $combination->value_types['string']::class === TNonEmptyNonspecificLiteralString::class ) { $combination->value_types['string'] = $type; } else { @@ -1251,7 +1246,7 @@ private static function scrapeIntProperties( string $type_key, Atomic $type, TypeCombination $combination, - int $literal_limit + int $literal_limit, ): void { if (isset($combination->value_types['array-key'])) { return; @@ -1309,8 +1304,8 @@ private static function scrapeIntProperties( if ($combination->ints || !isset($combination->value_types['int'])) { $combination->value_types['int'] = $type; } elseif (isset($combination->value_types['int']) - && get_class($combination->value_types['int']) - !== get_class($type) + && $combination->value_types['int']::class + !== $type::class ) { $combination->value_types['int'] = new TInt(); } @@ -1392,7 +1387,7 @@ private static function getClassLikes(Codebase $codebase, string $fq_classlike_n { try { $class_storage = $codebase->classlike_storage_provider->get($fq_classlike_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return []; } @@ -1421,7 +1416,7 @@ private static function getClassLikes(Codebase $codebase, string $fq_classlike_n private static function handleKeyedArrayEntries( TypeCombination $combination, bool $overwrite_empty_array, - bool $from_docblock + bool $from_docblock, ): array { $new_types = []; @@ -1494,7 +1489,6 @@ private static function handleKeyedArrayEntries( $sealed || $fallback_key_type === null || $fallback_value_type === null ? null : [$fallback_key_type, $fallback_value_type], - (bool)$combination->all_arrays_lists, $from_docblock, ); } else { @@ -1539,7 +1533,7 @@ private static function getArrayTypeFromGenericParams( bool $allow_mixed_union, Atomic $type, array $generic_type_params, - bool $from_docblock + bool $from_docblock, ): Atomic { if ($combination->objectlike_entries) { $objectlike_generic_type = null; @@ -1600,14 +1594,14 @@ private static function getArrayTypeFromGenericParams( $generic_type_params[1], $objectlike_generic_type, $codebase, - $overwrite_empty_array, + false, $allow_mixed_union, ); } } if ($combination->all_arrays_callable) { - $array_type = new TCallableArray($generic_type_params); + $array_type = new TCallableKeyedArray($generic_type_params); } elseif ($combination->array_always_filled || ($combination->array_sometimes_filled && $overwrite_empty_array) || ($combination->objectlike_entries diff --git a/src/Psalm/Internal/Type/TypeExpander.php b/src/Psalm/Internal/Type/TypeExpander.php index 8f7225ef37a..a4f48a7d726 100644 --- a/src/Psalm/Internal/Type/TypeExpander.php +++ b/src/Psalm/Internal/Type/TypeExpander.php @@ -1,5 +1,7 @@ * @psalm-suppress ConflictingReferenceConstraint, ReferenceConstraintViolation The output type is always Atomic @@ -122,14 +120,14 @@ public static function expandAtomic( Codebase $codebase, Atomic &$return_type, ?string $self_class, - $static_class_type, + string|TNamedObject|TTemplateParam|null $static_class_type, ?string $parent_class, bool $evaluate_class_constants = true, bool $evaluate_conditional_types = false, bool $final = false, bool $expand_generic = false, bool $expand_templates = false, - bool $throw_on_unresolvable_constant = false + bool $throw_on_unresolvable_constant = false, ): array { if ($return_type instanceof TEnumCase) { return [$return_type]; @@ -157,10 +155,7 @@ public static function expandAtomic( ); if ($extra_type instanceof TNamedObject && $extra_type->extra_types) { - $new_intersection_types = array_merge( - $new_intersection_types, - $extra_type->extra_types, - ); + $new_intersection_types = [...$new_intersection_types, ...$extra_type->extra_types]; $extra_type = $extra_type->setIntersectionTypes([]); } $extra_types[$extra_type->getKey()] = $extra_type; @@ -255,7 +250,7 @@ public static function expandAtomic( $return_type->const_name, ReflectionProperty::IS_PRIVATE, ); - } catch (CircularReferenceException $e) { + } catch (CircularReferenceException) { $class_constant = null; } @@ -326,28 +321,6 @@ public static function expandAtomic( ]; } - /** @psalm-suppress DeprecatedProperty For backwards compatibility, we have to keep this here. */ - foreach ($return_type->extra_types ?? [] as $alias) { - $more_recursively_fleshed_out_types = self::expandAtomic( - $codebase, - $alias, - $self_class, - $static_class_type, - $parent_class, - $evaluate_class_constants, - $evaluate_conditional_types, - $final, - $expand_generic, - $expand_templates, - $throw_on_unresolvable_constant, - ); - - $recursively_fleshed_out_types = [ - ...$more_recursively_fleshed_out_types, - ...$recursively_fleshed_out_types, - ]; - } - return $recursively_fleshed_out_types; } @@ -452,9 +425,7 @@ public static function expandAtomic( $throw_on_unresolvable_constant, ); } - if ($return_type instanceof TList) { - $return_type = $return_type->getKeyedArray(); - } + if ($return_type instanceof TArray || $return_type instanceof TGenericObject @@ -608,21 +579,19 @@ public static function expandAtomic( } /** - * @param string|TNamedObject|TTemplateParam|null $static_class_type * @param-out TNamedObject|TTemplateParam $return_type - * @return TNamedObject|TTemplateParam */ private static function expandNamedObject( Codebase $codebase, TNamedObject &$return_type, ?string $self_class, - $static_class_type, + string|TNamedObject|TTemplateParam|null $static_class_type, ?string $parent_class, bool $final = false, - bool &$expand_generic = false - ) { + bool &$expand_generic = false, + ): TNamedObject|TTemplateParam { if ($expand_generic - && get_class($return_type) === TNamedObject::class + && $return_type::class === TNamedObject::class && !$return_type->extra_types && $codebase->classOrInterfaceExists($return_type->value) ) { @@ -722,21 +691,20 @@ private static function expandNamedObject( } /** - * @param string|TNamedObject|TTemplateParam|null $static_class_type * @return non-empty-list */ private static function expandConditional( Codebase $codebase, TConditional &$return_type, ?string $self_class, - $static_class_type, + string|TNamedObject|TTemplateParam|null $static_class_type, ?string $parent_class, bool $evaluate_class_constants = true, bool $evaluate_conditional_types = false, bool $final = false, bool $expand_generic = false, bool $expand_templates = false, - bool $throw_on_unresolvable_constant = false + bool $throw_on_unresolvable_constant = false, ): array { $new_as_type = self::expandUnion( $codebase, @@ -934,14 +902,13 @@ private static function expandConditional( } /** - * @param string|TNamedObject|TTemplateParam|null $static_class_type * @return non-empty-list */ private static function expandPropertiesOf( Codebase $codebase, TPropertiesOf &$return_type, ?string $self_class, - $static_class_type + string|TNamedObject|TTemplateParam|null $static_class_type, ): array { if ($self_class) { $return_type = $return_type->replaceClassLike( @@ -1018,21 +985,20 @@ private static function expandPropertiesOf( /** * @param TKeyOf|TValueOf $return_type - * @param string|TNamedObject|TTemplateParam|null $static_class_type * @return non-empty-list */ private static function expandKeyOfValueOf( Codebase $codebase, Atomic &$return_type, ?string $self_class, - $static_class_type, + string|TNamedObject|TTemplateParam|null $static_class_type, ?string $parent_class, bool $evaluate_class_constants = true, bool $evaluate_conditional_types = false, bool $final = false, bool $expand_generic = false, bool $expand_templates = false, - bool $throw_on_unresolvable_constant = false + bool $throw_on_unresolvable_constant = false, ): array { // Expand class constants to their atomics $type_atomics = []; @@ -1075,7 +1041,7 @@ private static function expandKeyOfValueOf( false, $return_type instanceof TValueOf, ); - } catch (CircularReferenceException $e) { + } catch (CircularReferenceException) { return [$return_type]; } diff --git a/src/Psalm/Internal/Type/TypeParser.php b/src/Psalm/Internal/Type/TypeParser.php index 877f10855b7..b1656a2333f 100644 --- a/src/Psalm/Internal/Type/TypeParser.php +++ b/src/Psalm/Internal/Type/TypeParser.php @@ -1,5 +1,7 @@ value[0] === '"' || $parse_tree->value[0] === '\'') { @@ -454,7 +457,7 @@ private static function getGenericParamClass( string $param_name, Union &$as, string $defining_class, - bool $from_docblock = false + bool $from_docblock = false, ): TTemplateParamClass { if ($as->hasMixed()) { return new TTemplateParamClass( @@ -567,7 +570,6 @@ public static function getComputedIntsFromMask(array $potential_ints, bool $from /** * @param array> $template_type_map * @param array $type_aliases - * @return Atomic|Union * @throws TypeParseTreeException * @psalm-suppress ComplexMethod to be refactored */ @@ -576,8 +578,8 @@ private static function getTypeFromGenericTree( Codebase $codebase, array $template_type_map, array $type_aliases, - bool $from_docblock = false - ) { + bool $from_docblock = false, + ): Atomic|Union { $generic_type = $parse_tree->value; $generic_params = []; @@ -960,7 +962,7 @@ private static function getTypeFromGenericTree( if (!$atomic_type instanceof TLiteralInt && !($atomic_type instanceof TClassConstant - && strpos($atomic_type->const_name, '*') === false) + && !str_contains($atomic_type->const_name, '*')) ) { throw new TypeParseTreeException( 'int-mask types must all be integer values or scalar class constants', @@ -1000,7 +1002,7 @@ private static function getTypeFromGenericTree( 'Invalid reference passed to int-mask-of', ); } elseif ($param_type instanceof TClassConstant - && strpos($param_type->const_name, '*') === false + && !str_contains($param_type->const_name, '*') ) { throw new TypeParseTreeException( 'Class constant passed to int-mask-of must be a wildcard type', @@ -1019,7 +1021,7 @@ private static function getTypeFromGenericTree( $get_int_range_bound = static function ( ParseTree $parse_tree, Union $generic_param, - string $bound_name + string $bound_name, ): ?int { if (!$parse_tree instanceof Value || count($generic_param->getAtomicTypes()) > 1 @@ -1078,7 +1080,7 @@ private static function getTypeFromUnionTree( Codebase $codebase, array $template_type_map, array $type_aliases, - bool $from_docblock + bool $from_docblock, ): Union { $has_null = false; @@ -1144,7 +1146,7 @@ private static function getTypeFromIntersectionTree( Codebase $codebase, array $template_type_map, array $type_aliases, - bool $from_docblock + bool $from_docblock, ): Atomic { $intersection_types = []; @@ -1221,6 +1223,7 @@ private static function getTypeFromIntersectionTree( } $first_type = array_shift($keyed_intersection_types); + assert($first_type !== null); // Keyed array intersection are merged together and are not combinable with object-types if ($first_type instanceof TKeyedArray) { @@ -1256,7 +1259,6 @@ private static function getTypeFromIntersectionTree( /** * @param array> $template_type_map * @param array $type_aliases - * @return TCallable|TClosure * @throws TypeParseTreeException */ private static function getTypeFromCallableTree( @@ -1264,8 +1266,8 @@ private static function getTypeFromCallableTree( Codebase $codebase, array $template_type_map, array $type_aliases, - bool $from_docblock - ) { + bool $from_docblock, + ): TCallable|TClosure { $params = []; foreach ($parse_tree->children as $child_tree) { @@ -1292,7 +1294,7 @@ private static function getTypeFromCallableTree( $param_name = $child_tree->name ?? ''; } else { if ($child_tree instanceof Value && strpos($child_tree->value, '$') > 0) { - $child_tree->value = preg_replace('/(.+)\$.*/', '$1', $child_tree->value); + $child_tree->value = (string) preg_replace('/(.+)\$.*/', '$1', $child_tree->value); } $tree_type = self::getTypeFromTree( @@ -1320,7 +1322,7 @@ private static function getTypeFromCallableTree( $params[] = $param; } - $pure = strpos($parse_tree->value, 'pure-') === 0 ? true : null; + $pure = str_starts_with($parse_tree->value, 'pure-') ? true : null; if (in_array(strtolower($parse_tree->value), ['closure', '\closure', 'pure-closure'], true)) { return new TClosure('Closure', $params, null, $pure, [], [], $from_docblock); @@ -1336,7 +1338,7 @@ private static function getTypeFromCallableTree( private static function getTypeFromIndexAccessTree( IndexedAccessTree $parse_tree, array $template_type_map, - bool $from_docblock + bool $from_docblock, ): TTemplateIndexedAccess { if (!isset($parse_tree->children[0]) || !$parse_tree->children[0] instanceof Value) { throw new TypeParseTreeException('Unrecognised indexed access'); @@ -1371,7 +1373,7 @@ private static function getTypeFromIndexAccessTree( $array_defining_class = array_keys($template_type_map[$array_param_name])[0]; if ($offset_defining_class !== $array_defining_class - && strpos($offset_defining_class, 'fn-') !== 0 + && !str_starts_with($offset_defining_class, 'fn-') ) { throw new TypeParseTreeException('Template params are defined in different locations'); } @@ -1387,7 +1389,6 @@ private static function getTypeFromIndexAccessTree( /** * @param array> $template_type_map * @param array $type_aliases - * @return TCallableKeyedArray|TKeyedArray|TObjectWithProperties|TArray * @throws TypeParseTreeException */ private static function getTypeFromKeyedArrayTree( @@ -1395,8 +1396,8 @@ private static function getTypeFromKeyedArrayTree( Codebase $codebase, array $template_type_map, array $type_aliases, - bool $from_docblock - ) { + bool $from_docblock, + ): TCallableKeyedArray|TKeyedArray|TObjectWithProperties|TArray { $properties = []; $class_strings = []; @@ -1523,7 +1524,7 @@ private static function getTypeFromKeyedArrayTree( return new TObjectWithProperties($properties, [], [], $from_docblock); } - $callable = strpos($type, 'callable-') === 0; + $callable = str_starts_with($type, 'callable-'); $class = TKeyedArray::class; if ($callable) { $class = TCallableKeyedArray::class; @@ -1598,7 +1599,7 @@ private static function extractIntersectionKey(Atomic $intersection_type): strin */ private static function extractKeyedIntersectionTypes( Codebase $codebase, - array $intersection_types + array $intersection_types, ): array { $keyed_intersection_types = []; $callable_intersection = null; @@ -1636,7 +1637,7 @@ private static function extractKeyedIntersectionTypes( continue; } - if (get_class($intersection_type) === TObject::class) { + if ($intersection_type::class === TObject::class) { continue; } @@ -1652,7 +1653,7 @@ private static function extractKeyedIntersectionTypes( throw new TypeParseTreeException( 'Intersection types must be all objects, ' - . get_class($intersection_type) . ' provided', + . $intersection_type::class . ' provided', ); } @@ -1730,7 +1731,7 @@ private static function getTypeFromKeyedArrays( array $intersection_types, Atomic $first_type, Atomic $last_type, - bool $from_docblock + bool $from_docblock, ): Atomic { /** @var non-empty-array */ $properties = []; diff --git a/src/Psalm/Internal/Type/TypeTokenizer.php b/src/Psalm/Internal/Type/TypeTokenizer.php index ce0660972dd..1b2dafe7031 100644 --- a/src/Psalm/Internal/Type/TypeTokenizer.php +++ b/src/Psalm/Internal/Type/TypeTokenizer.php @@ -1,5 +1,7 @@ $type_string_lc, + default => match ($type_string) { + 'boolean' => $analysis_php_version_id !== null ? $type_string : 'bool', + 'integer' => $analysis_php_version_id !== null ? $type_string : 'int', + 'double', 'real' => $analysis_php_version_id !== null ? $type_string : 'float', + default => $type_string, + }, + }; } /** @@ -360,7 +342,7 @@ public static function getFullyQualifiedTokens( ?array $type_aliases = null, ?string $self_fqcln = null, ?string $parent_fqcln = null, - bool $allow_assertions = false + bool $allow_assertions = false, ): array { $type_tokens = self::tokenize($string_type); @@ -407,7 +389,7 @@ public static function getFullyQualifiedTokens( } if (strpos($string_type_token[0], '$')) { - $string_type_token[0] = preg_replace('/(.+)\$.*/', '$1', $string_type_token[0]); + $string_type_token[0] = (string) preg_replace('/(.+)\$.*/', '$1', $string_type_token[0]); } $fixed_token = !isset($type_tokens[$i + 1]) || $type_tokens[$i + 1][0] !== '(' diff --git a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php index 0dee9e263a9..b1a1d8de366 100644 --- a/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/CanContainObjectTypeVisitor.php @@ -1,5 +1,7 @@ codebase = $codebase; + public function __construct( + private readonly Codebase $codebase, + ) { } protected function enterNode(TypeNode $type): ?int diff --git a/src/Psalm/Internal/TypeVisitor/ClasslikeReplacer.php b/src/Psalm/Internal/TypeVisitor/ClasslikeReplacer.php index ce54f8959b0..5ae357812d8 100644 --- a/src/Psalm/Internal/TypeVisitor/ClasslikeReplacer.php +++ b/src/Psalm/Internal/TypeVisitor/ClasslikeReplacer.php @@ -1,5 +1,7 @@ old = strtolower($old); - $this->new = $new; } protected function enterNode(TypeNode &$type): ?int diff --git a/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php b/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php index 7a93f182713..434b696a215 100644 --- a/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/ContainsClassLikeVisitor.php @@ -1,5 +1,7 @@ fq_classlike_name = $fq_classlike_name; + public function __construct( + private readonly string $fq_classlike_name, + ) { } /** diff --git a/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php b/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php index dfb3367740b..dfea8289159 100644 --- a/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php +++ b/src/Psalm/Internal/TypeVisitor/ContainsLiteralVisitor.php @@ -1,5 +1,7 @@ from_docblock = $from_docblock; + public function __construct( + private readonly bool $from_docblock, + ) { } /** * @return self::STOP_TRAVERSAL|self::DONT_TRAVERSE_CHILDREN|null diff --git a/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php b/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php index 36db3898416..c363aaee609 100644 --- a/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php +++ b/src/Psalm/Internal/TypeVisitor/TemplateTypeCollector.php @@ -1,5 +1,7 @@ - */ - private array $suppressed_issues; - - /** - * @var array - */ - private array $phantom_classes; - - private bool $inferred; - - private bool $inherited; - - private bool $prevent_template_covariance; - private bool $has_errors = false; - private ?string $calling_method_id = null; - /** * @param array $suppressed_issues * @param array $phantom_classes */ public function __construct( - StatementsSource $source, - CodeLocation $code_location, - array $suppressed_issues, - array $phantom_classes = [], - bool $inferred = true, - bool $inherited = false, - bool $prevent_template_covariance = false, - ?string $calling_method_id = null + private readonly StatementsSource $source, + private readonly CodeLocation $code_location, + private readonly array $suppressed_issues, + private array $phantom_classes = [], + private readonly bool $inferred = true, + private readonly bool $inherited = false, + private bool $prevent_template_covariance = false, + private readonly ?string $calling_method_id = null, ) { - $this->source = $source; - $this->code_location = $code_location; - $this->suppressed_issues = $suppressed_issues; - $this->phantom_classes = $phantom_classes; - $this->inferred = $inferred; - $this->inherited = $inherited; - $this->prevent_template_covariance = $prevent_template_covariance; - $this->calling_method_id = $calling_method_id; } /** @@ -213,7 +186,7 @@ private function checkGenericParams(TGenericObject $atomic): void try { $class_storage = $codebase->classlike_storage_provider->get(strtolower($atomic->value)); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return; } @@ -321,7 +294,7 @@ public function checkScalarClassConstant(TClassConstant $atomic): void } $const_name = $atomic->const_name; - if (strpos($const_name, '*') !== false) { + if (str_contains($const_name, '*')) { TypeExpander::expandAtomic( $this->source->getCodebase(), $atomic, @@ -358,7 +331,7 @@ public function checkScalarClassConstant(TClassConstant $atomic): void public function checkTemplateParam(TTemplateParam $atomic): void { if ($this->prevent_template_covariance - && strpos($atomic->defining_class, 'fn-') !== 0 + && !str_starts_with($atomic->defining_class, 'fn-') && $atomic->defining_class !== 'class-string-map' ) { $codebase = $this->source->getCodebase(); diff --git a/src/Psalm/Internal/TypeVisitor/TypeLocalizer.php b/src/Psalm/Internal/TypeVisitor/TypeLocalizer.php index 4a2866e93da..dcc40b5451c 100644 --- a/src/Psalm/Internal/TypeVisitor/TypeLocalizer.php +++ b/src/Psalm/Internal/TypeVisitor/TypeLocalizer.php @@ -1,5 +1,7 @@ > - */ - private array $extends; - private string $base_fq_class_name; - /** * @param array> $extends */ public function __construct( - array $extends, - string $base_fq_class_name + private array $extends, + private readonly string $base_fq_class_name, ) { - $this->extends = $extends; - $this->base_fq_class_name = $base_fq_class_name; } protected function enterNode(TypeNode &$type): ?int diff --git a/src/Psalm/Internal/TypeVisitor/TypeScanner.php b/src/Psalm/Internal/TypeVisitor/TypeScanner.php index 524044f6358..dd480f45b85 100644 --- a/src/Psalm/Internal/TypeVisitor/TypeScanner.php +++ b/src/Psalm/Internal/TypeVisitor/TypeScanner.php @@ -1,5 +1,7 @@ - */ - private array $phantom_classes; - /** * @param array $phantom_classes */ public function __construct( - Scanner $scanner, - ?FileStorage $file_storage, - array $phantom_classes + private readonly Scanner $scanner, + private readonly ?FileStorage $file_storage, + private array $phantom_classes, ) { - $this->scanner = $scanner; - $this->file_storage = $file_storage; - $this->phantom_classes = $phantom_classes; } protected function enterNode(TypeNode $type): ?int diff --git a/src/Psalm/Internal/VersionUtils.php b/src/Psalm/Internal/VersionUtils.php index ac0d6575809..fcf7f279f86 100644 --- a/src/Psalm/Internal/VersionUtils.php +++ b/src/Psalm/Internal/VersionUtils.php @@ -1,5 +1,7 @@ self::getVersion(self::PSALM_PACKAGE), self::PHP_PARSER_PACKAGE => self::getVersion(self::PHP_PARSER_PACKAGE), ]; - } catch (OutOfBoundsException $ex) { + } catch (OutOfBoundsException) { } return null; } diff --git a/src/Psalm/Issue/AbstractInstantiation.php b/src/Psalm/Issue/AbstractInstantiation.php index d6aa9f3b540..2a8296f82b0 100644 --- a/src/Psalm/Issue/AbstractInstantiation.php +++ b/src/Psalm/Issue/AbstractInstantiation.php @@ -1,5 +1,7 @@ function_id = $function_id ? strtolower($function_id) : null; diff --git a/src/Psalm/Issue/ArgumentTypeCoercion.php b/src/Psalm/Issue/ArgumentTypeCoercion.php index 516fd4104d6..26833a207dc 100644 --- a/src/Psalm/Issue/ArgumentTypeCoercion.php +++ b/src/Psalm/Issue/ArgumentTypeCoercion.php @@ -1,5 +1,7 @@ const_id = $const_id; } } diff --git a/src/Psalm/Issue/ClassIssue.php b/src/Psalm/Issue/ClassIssue.php index 3efaa7e5785..d88d50d5178 100644 --- a/src/Psalm/Issue/ClassIssue.php +++ b/src/Psalm/Issue/ClassIssue.php @@ -1,22 +1,18 @@ fq_classlike_name = $fq_classlike_name; } } diff --git a/src/Psalm/Issue/CodeIssue.php b/src/Psalm/Issue/CodeIssue.php index ce4fcbc469b..5a321fd1d9e 100644 --- a/src/Psalm/Issue/CodeIssue.php +++ b/src/Psalm/Issue/CodeIssue.php @@ -1,5 +1,7 @@ */ public const SHORTCODE = 0; - /** - * @var CodeLocation - * @readonly - */ - public $code_location; - - /** - * @var string - * @readonly - */ - public $message; - - /** - * @var ?string - */ - public $dupe_key; + public ?string $dupe_key = null; public function __construct( - string $message, - CodeLocation $code_location + public readonly string $message, + public readonly CodeLocation $code_location, ) { - $this->code_location = $code_location; - $this->message = $message; } public function getShortLocationWithPrevious(): string diff --git a/src/Psalm/Issue/ComplexFunction.php b/src/Psalm/Issue/ComplexFunction.php index 6db2fec3865..0f42c0630fc 100644 --- a/src/Psalm/Issue/ComplexFunction.php +++ b/src/Psalm/Issue/ComplexFunction.php @@ -1,5 +1,7 @@ function_id = strtolower($function_id); diff --git a/src/Psalm/Issue/IfThisIsMismatch.php b/src/Psalm/Issue/IfThisIsMismatch.php index 508b2176eeb..e56bd547631 100644 --- a/src/Psalm/Issue/IfThisIsMismatch.php +++ b/src/Psalm/Issue/IfThisIsMismatch.php @@ -1,5 +1,7 @@ method_id = strtolower($method_id); diff --git a/src/Psalm/Issue/MethodSignatureMismatch.php b/src/Psalm/Issue/MethodSignatureMismatch.php index d3ba51725e4..5a5a31a5d3d 100644 --- a/src/Psalm/Issue/MethodSignatureMismatch.php +++ b/src/Psalm/Issue/MethodSignatureMismatch.php @@ -1,5 +1,7 @@ code_location = $code_location; - $this->message = $message; + parent::__construct($message, $code_location); $this->function_id = $function_id ? strtolower($function_id) : null; $this->origin_location = $origin_location; } diff --git a/src/Psalm/Issue/MixedArgumentTypeCoercion.php b/src/Psalm/Issue/MixedArgumentTypeCoercion.php index c9af86b0760..078a4e916b2 100644 --- a/src/Psalm/Issue/MixedArgumentTypeCoercion.php +++ b/src/Psalm/Issue/MixedArgumentTypeCoercion.php @@ -1,5 +1,7 @@ code_location = $code_location; - $this->message = $message; + parent::__construct($message, $code_location); $this->function_id = $function_id ? strtolower($function_id) : null; $this->origin_location = $origin_location; } diff --git a/src/Psalm/Issue/MixedArrayAccess.php b/src/Psalm/Issue/MixedArrayAccess.php index bb827b7f841..407d3822dbb 100644 --- a/src/Psalm/Issue/MixedArrayAccess.php +++ b/src/Psalm/Issue/MixedArrayAccess.php @@ -1,5 +1,7 @@ code_location = $code_location; - $this->message = $message; + parent::__construct($message, $code_location); $this->origin_location = $origin_location; } diff --git a/src/Psalm/Issue/MixedMethodCall.php b/src/Psalm/Issue/MixedMethodCall.php index 939f787ef50..786f937a3ea 100644 --- a/src/Psalm/Issue/MixedMethodCall.php +++ b/src/Psalm/Issue/MixedMethodCall.php @@ -1,5 +1,7 @@ origin_location = $origin_location; diff --git a/src/Psalm/Issue/MixedReturnStatement.php b/src/Psalm/Issue/MixedReturnStatement.php index 758258911a9..eb1cc2bb962 100644 --- a/src/Psalm/Issue/MixedReturnStatement.php +++ b/src/Psalm/Issue/MixedReturnStatement.php @@ -1,5 +1,7 @@ dupe_key = strtolower($method_id); diff --git a/src/Psalm/Issue/PossiblyUnusedParam.php b/src/Psalm/Issue/PossiblyUnusedParam.php index 253af21166a..079bb9f1363 100644 --- a/src/Psalm/Issue/PossiblyUnusedParam.php +++ b/src/Psalm/Issue/PossiblyUnusedParam.php @@ -1,5 +1,7 @@ dupe_key = $property_id; diff --git a/src/Psalm/Issue/PossiblyUnusedReturnValue.php b/src/Psalm/Issue/PossiblyUnusedReturnValue.php index 975b3f9a228..c16ca5ca614 100644 --- a/src/Psalm/Issue/PossiblyUnusedReturnValue.php +++ b/src/Psalm/Issue/PossiblyUnusedReturnValue.php @@ -1,5 +1,7 @@ property_id = $property_id; } } diff --git a/src/Psalm/Issue/PropertyNotSetInConstructor.php b/src/Psalm/Issue/PropertyNotSetInConstructor.php index 187a48f9347..1c057ec3422 100644 --- a/src/Psalm/Issue/PropertyNotSetInConstructor.php +++ b/src/Psalm/Issue/PropertyNotSetInConstructor.php @@ -1,5 +1,7 @@ dupe_key = $property_id; diff --git a/src/Psalm/Issue/PropertyTypeCoercion.php b/src/Psalm/Issue/PropertyTypeCoercion.php index 457dd52ba38..eee59f8306b 100644 --- a/src/Psalm/Issue/PropertyTypeCoercion.php +++ b/src/Psalm/Issue/PropertyTypeCoercion.php @@ -1,5 +1,7 @@ */ public const SHORTCODE = 205; - /** - * @var string - * @readonly - */ - public $journey_text; - - /** - * @var list - * @readonly - */ - public $journey = []; - /** * @param list $journey */ public function __construct( string $message, CodeLocation $code_location, - array $journey, - string $journey_text + public readonly array $journey, + public readonly string $journey_text, ) { parent::__construct($message, $code_location); - - $this->journey = $journey; - $this->journey_text = $journey_text; } /** @@ -58,7 +45,7 @@ public function getTaintTrace(): array public static function nodeToDataFlowNodeData( CodeLocation $location, - string $label + string $label, ): DataFlowNodeData { $selection_bounds = $location->getSelectionBounds(); $snippet_bounds = $location->getSnippetBounds(); diff --git a/src/Psalm/Issue/TaintedLdap.php b/src/Psalm/Issue/TaintedLdap.php index 4ab8e32e2ff..1fb8cc55461 100644 --- a/src/Psalm/Issue/TaintedLdap.php +++ b/src/Psalm/Issue/TaintedLdap.php @@ -1,5 +1,7 @@ dupe_key = strtolower($method_id); diff --git a/src/Psalm/Issue/UnusedMethodCall.php b/src/Psalm/Issue/UnusedMethodCall.php index 067b13af8e5..0a4cf9cc945 100644 --- a/src/Psalm/Issue/UnusedMethodCall.php +++ b/src/Psalm/Issue/UnusedMethodCall.php @@ -1,5 +1,7 @@ dupe_key = $property_id; diff --git a/src/Psalm/Issue/UnusedPsalmSuppress.php b/src/Psalm/Issue/UnusedPsalmSuppress.php index 570012ae719..012ddcc90a8 100644 --- a/src/Psalm/Issue/UnusedPsalmSuppress.php +++ b/src/Psalm/Issue/UnusedPsalmSuppress.php @@ -1,5 +1,7 @@ var_name = strtolower($var_name); diff --git a/src/Psalm/IssueBuffer.php b/src/Psalm/IssueBuffer.php index 45f8d53e9cf..15803088934 100644 --- a/src/Psalm/IssueBuffer.php +++ b/src/Psalm/IssueBuffer.php @@ -1,5 +1,7 @@ > */ - protected static $issues_data = []; + private static array $issues_data = []; /** * @var array */ - protected static $console_issues = []; + private static array $console_issues = []; /** * @var array */ - protected static $fixable_issue_counts = []; + private static array $fixable_issue_counts = []; - /** - * @var int - */ - protected static $error_count = 0; + private static int $error_count = 0; /** * @var array */ - protected static $emitted = []; + private static array $emitted = []; - /** @var int */ - protected static $recording_level = 0; + private static int $recording_level = 0; /** @var array> */ - protected static $recorded_issues = []; + private static array $recorded_issues = []; /** * @var array> */ - protected static $unused_suppressions = []; + private static array $unused_suppressions = []; /** * @var array> */ - protected static $used_suppressions = []; + private static array $used_suppressions = []; /** @var array */ private static array $server = []; @@ -163,7 +162,7 @@ public static function maybeAdd(CodeIssue $e, array $suppressed_issues = [], boo */ public static function addUnusedSuppression(string $file_path, int $offset, string $issue_type): void { - if (strpos($issue_type, 'Tainted') === 0) { + if (str_starts_with($issue_type, 'Tainted')) { return; } @@ -190,7 +189,7 @@ public static function isSuppressed(CodeIssue $e, array $suppressed_issues = []) { $config = Config::getInstance(); - $fqcn_parts = explode('\\', get_class($e)); + $fqcn_parts = explode('\\', $e::class); $issue_type = array_pop($fqcn_parts); $file_path = $e->getFilePath(); @@ -266,14 +265,14 @@ public static function add(CodeIssue $e, bool $is_fixable = false): bool $project_analyzer = ProjectAnalyzer::getInstance(); $codebase = $project_analyzer->getCodebase(); - $fqcn_parts = explode('\\', get_class($e)); + $fqcn_parts = explode('\\', $e::class); $issue_type = array_pop($fqcn_parts); if (!$project_analyzer->show_issues) { return false; } - $is_tainted = strpos($issue_type, 'Tainted') === 0; + $is_tainted = str_starts_with($issue_type, 'Tainted'); if ($codebase->taint_flow_graph && !$is_tainted) { return false; @@ -542,7 +541,7 @@ public static function finish( bool $is_full, float $start_time, bool $add_stats = false, - array $issue_baseline = [] + array $issue_baseline = [], ): void { if (!$project_analyzer->stdout_report_options) { throw new UnexpectedValueException('Cannot finish without stdout report options'); @@ -650,6 +649,46 @@ public static function finish( } } + if ($codebase->config->find_unused_issue_handler_suppression) { + if ($is_full && !$codebase->diff_run) { + foreach ($codebase->config->getIssueHandlers() as $type => $handler) { + foreach ($handler->getFilters() as $filter) { + if ($filter->suppressions > 0 && $filter->getErrorLevel() == Config::REPORT_SUPPRESS) { + continue; + } + $issues_data['config'][] = new IssueData( + IssueData::SEVERITY_ERROR, + 0, + 0, + UnusedIssueHandlerSuppression::getIssueType(), + sprintf( + 'Suppressed issue type "%s" for %s was not thrown.', + $type, + str_replace( + $codebase->config->base_dir, + '', + implode(', ', [...$filter->getFiles(), ...$filter->getDirectories()]), + ), + ), + $codebase->config->source_filename ?? '', + '', + '', + '', + 0, + 0, + 0, + 0, + 0, + 0, + UnusedIssueHandlerSuppression::SHORTCODE, + UnusedIssueHandlerSuppression::ERROR_LEVEL, + ); + } + } + } else { + } + } + echo self::getOutput( $issues_data, $project_analyzer->stdout_report_options, @@ -673,7 +712,7 @@ public static function finish( try { $source_control_info = (new GitInfoCollector())->collect(); - } catch (RuntimeException $e) { + } catch (RuntimeException) { // do nothing } @@ -791,6 +830,15 @@ public static function finish( echo "\n"; } } + + if ($codebase->config->find_unused_issue_handler_suppression && (!$is_full || $codebase->diff_run)) { + fwrite( + STDERR, + PHP_EOL . 'To whom it may concern: Psalm cannot detect unused issue handler suppressions when' + . PHP_EOL . 'analyzing individual files and folders or running in diff mode. Run on the full' + . PHP_EOL . 'project with diff mode off to enable unused issue handler detection.' . PHP_EOL, + ); + } } if ($is_full && $start_time) { @@ -854,7 +902,7 @@ public static function printSuccessMessage(ProjectAnalyzer $project_analyzer): v public static function getOutput( array $issues_data, ReportOptions $report_options, - array $mixed_counts = [0, 0] + array $mixed_counts = [0, 0], ): string { $total_expression_count = $mixed_counts[0] + $mixed_counts[1]; $mixed_expression_count = $mixed_counts[0]; @@ -863,89 +911,100 @@ public static function getOutput( $format = $report_options->format; - switch ($format) { - case Report::TYPE_COMPACT: - $output = new CompactReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_EMACS: - $output = new EmacsReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_TEXT: - $output = new TextReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_JSON: - $output = new JsonReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_BY_ISSUE_LEVEL: - $output = new ByIssueLevelAndTypeReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_JSON_SUMMARY: - $output = new JsonSummaryReport( - $normalized_data, - self::$fixable_issue_counts, - $report_options, - $mixed_expression_count, - $total_expression_count, - ); - break; - - case Report::TYPE_SONARQUBE: - $output = new SonarqubeReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_PYLINT: - $output = new PylintReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_CHECKSTYLE: - $output = new CheckstyleReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_XML: - $output = new XmlReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_JUNIT: - $output = new JunitReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_CONSOLE: - $output = new ConsoleReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_GITHUB_ACTIONS: - $output = new GithubActionsReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_PHP_STORM: - $output = new PhpStormReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_SARIF: - $output = new SarifReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_CODECLIMATE: - $output = new CodeClimateReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - case Report::TYPE_COUNT: - $output = new CountReport($normalized_data, self::$fixable_issue_counts, $report_options); - break; - - default: - throw new RuntimeException('Unexpected report format: ' . $report_options->format); - } + $output = match ($format) { + Report::TYPE_COMPACT => new CompactReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_EMACS => new EmacsReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_TEXT => new TextReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_JSON => new JsonReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_BY_ISSUE_LEVEL => new ByIssueLevelAndTypeReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_JSON_SUMMARY => new JsonSummaryReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + $mixed_expression_count, + $total_expression_count, + ), + Report::TYPE_SONARQUBE => new SonarqubeReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_PYLINT => new PylintReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_CHECKSTYLE => new CheckstyleReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_XML => new XmlReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_JUNIT => new JunitReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_CONSOLE => new ConsoleReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_GITHUB_ACTIONS => new GithubActionsReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_PHP_STORM => new PhpStormReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_SARIF => new SarifReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_CODECLIMATE => new CodeClimateReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + Report::TYPE_COUNT => new CountReport( + $normalized_data, + self::$fixable_issue_counts, + $report_options, + ), + }; return $output->create(); } - protected static function alreadyEmitted(string $message): bool + public static function alreadyEmitted(string $message): bool { $sham = sha1($message); diff --git a/src/Psalm/Node/Scalar/VirtualDNumber.php b/src/Psalm/Node/Scalar/VirtualDNumber.php deleted file mode 100644 index 9a95e9b0312..00000000000 --- a/src/Psalm/Node/Scalar/VirtualDNumber.php +++ /dev/null @@ -1,13 +0,0 @@ -context = $context; - $this->statements_analyzer = $statements_analyzer; + public function __construct( + private readonly Context $context, + private readonly StatementsAnalyzer $statements_analyzer, + ) { } - /** - * @return false|Union - */ - public function infer(PhpParser\Node\Arg $arg) + public function infer(PhpParser\Node\Arg $arg): null|Union { $already_inferred_type = $this->statements_analyzer->node_data->getType($arg->value); @@ -37,7 +31,7 @@ public function infer(PhpParser\Node\Arg $arg) } if (ExpressionAnalyzer::analyze($this->statements_analyzer, $arg->value, $this->context) === false) { - return false; + return null; } return $this->statements_analyzer->node_data->getType($arg->value) ?? Type::getMixed(); diff --git a/src/Psalm/Plugin/DynamicTemplateProvider.php b/src/Psalm/Plugin/DynamicTemplateProvider.php index c3778d47c11..4049309d419 100644 --- a/src/Psalm/Plugin/DynamicTemplateProvider.php +++ b/src/Psalm/Plugin/DynamicTemplateProvider.php @@ -10,14 +10,12 @@ final class DynamicTemplateProvider { - private string $defining_class; - /** * @internal */ - public function __construct(string $defining_class) - { - $this->defining_class = $defining_class; + public function __construct( + private readonly string $defining_class, + ) { } /** diff --git a/src/Psalm/Plugin/EventHandler/AddTaintsInterface.php b/src/Psalm/Plugin/EventHandler/AddTaintsInterface.php index 66909acfeab..e00d65e8e29 100644 --- a/src/Psalm/Plugin/EventHandler/AddTaintsInterface.php +++ b/src/Psalm/Plugin/EventHandler/AddTaintsInterface.php @@ -1,5 +1,7 @@ expr = $expr; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; } - public function getExpr(): Expr + public function getExpr(): ArrayItem|Expr { return $this->expr; } diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterAnalysisEvent.php index 60db94732b0..977616588bd 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterAnalysisEvent.php @@ -1,5 +1,7 @@ > where string key is a filepath - */ - private array $issues; - private array $build_info; - private ?SourceControlInfo $source_control_info; - /** * Called after analysis is complete * @@ -23,15 +17,11 @@ final class AfterAnalysisEvent * @internal */ public function __construct( - Codebase $codebase, - array $issues, - array $build_info, - ?SourceControlInfo $source_control_info = null + private readonly Codebase $codebase, + private readonly array $issues, + private readonly array $build_info, + private readonly ?SourceControlInfo $source_control_info = null, ) { - $this->codebase = $codebase; - $this->issues = $issues; - $this->build_info = $build_info; - $this->source_control_info = $source_control_info; } public function getCodebase(): Codebase diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeAnalysisEvent.php index a6c51581eae..20839c14d41 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeAnalysisEvent.php @@ -1,5 +1,7 @@ stmt = $stmt; - $this->classlike_storage = $classlike_storage; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; } public function getStmt(): Node\Stmt\ClassLike diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeExistenceCheckEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeExistenceCheckEvent.php index d80732bcb8b..7e38a0c5a9e 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeExistenceCheckEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeExistenceCheckEvent.php @@ -1,5 +1,7 @@ fq_class_name = $fq_class_name; - $this->code_location = $code_location; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; } public function getFqClassName(): string diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeVisitEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeVisitEvent.php index 3861baf0165..e6630ff4bc4 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeVisitEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterClassLikeVisitEvent.php @@ -1,5 +1,7 @@ stmt = $stmt; - $this->storage = $storage; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; } public function getStmt(): ClassLike diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterCodebasePopulatedEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterCodebasePopulatedEvent.php index 238bf2489b5..f2bdd176483 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterCodebasePopulatedEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterCodebasePopulatedEvent.php @@ -1,21 +1,21 @@ codebase = $codebase; + public function __construct( + private readonly Codebase $codebase, + ) { } public function getCodebase(): Codebase diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterEveryFunctionCallAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterEveryFunctionCallAnalysisEvent.php index 410b6b1149b..e8b098c4d61 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterEveryFunctionCallAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterEveryFunctionCallAnalysisEvent.php @@ -1,5 +1,7 @@ expr = $expr; - $this->function_id = $function_id; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; } public function getExpr(): FuncCall diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterExpressionAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterExpressionAnalysisEvent.php index 25bb8dab8d3..a14af785993 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterExpressionAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterExpressionAnalysisEvent.php @@ -1,5 +1,7 @@ expr = $expr; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; } public function getExpr(): Expr diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterFileAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterFileAnalysisEvent.php index ec468ced212..915d2efd0bd 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterFileAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterFileAnalysisEvent.php @@ -1,5 +1,7 @@ statements_source = $statements_source; - $this->file_context = $file_context; - $this->file_storage = $file_storage; - $this->codebase = $codebase; - $this->stmts = $stmts; } public function getStatementsSource(): StatementsSource diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterFunctionCallAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterFunctionCallAnalysisEvent.php index 96b463c7d65..97f9b1471ec 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterFunctionCallAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterFunctionCallAnalysisEvent.php @@ -1,5 +1,7 @@ expr = $expr; - $this->function_id = $function_id; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->return_type_candidate = $return_type_candidate; - $this->file_replacements = $file_replacements; } public function getExpr(): FuncCall diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterFunctionLikeAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterFunctionLikeAnalysisEvent.php index 2c5b1fa81cb..527a13dae51 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterFunctionLikeAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterFunctionLikeAnalysisEvent.php @@ -1,5 +1,7 @@ stmt = $stmt; - $this->functionlike_storage = $functionlike_storage; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; - $this->node_type_provider = $node_type_provider; - $this->context = $context; } public function getStmt(): Node\FunctionLike diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterMethodCallAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterMethodCallAnalysisEvent.php index 7a071d6d94a..179e2fb575e 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterMethodCallAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterMethodCallAnalysisEvent.php @@ -1,5 +1,7 @@ expr = $expr; - $this->method_id = $method_id; - $this->appearing_method_id = $appearing_method_id; - $this->declaring_method_id = $declaring_method_id; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; - $this->return_type_candidate = $return_type_candidate; } /** diff --git a/src/Psalm/Plugin/EventHandler/Event/AfterStatementAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/AfterStatementAnalysisEvent.php index f4b5473f9df..9c96b69a59e 100644 --- a/src/Psalm/Plugin/EventHandler/Event/AfterStatementAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/AfterStatementAnalysisEvent.php @@ -1,5 +1,7 @@ stmt = $stmt; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; } public function getStmt(): Stmt diff --git a/src/Psalm/Plugin/EventHandler/Event/BeforeAddIssueEvent.php b/src/Psalm/Plugin/EventHandler/Event/BeforeAddIssueEvent.php index 9e354ed900d..d5664a18dd2 100644 --- a/src/Psalm/Plugin/EventHandler/Event/BeforeAddIssueEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/BeforeAddIssueEvent.php @@ -9,16 +9,12 @@ final class BeforeAddIssueEvent { - private CodeIssue $issue; - private bool $fixable; - private Codebase $codebase; - /** @internal */ - public function __construct(CodeIssue $issue, bool $fixable, Codebase $codebase) - { - $this->issue = $issue; - $this->fixable = $fixable; - $this->codebase = $codebase; + public function __construct( + private readonly CodeIssue $issue, + private readonly bool $fixable, + private readonly Codebase $codebase, + ) { } public function getIssue(): CodeIssue diff --git a/src/Psalm/Plugin/EventHandler/Event/BeforeExpressionAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/BeforeExpressionAnalysisEvent.php index e83c44e5fca..f3eb1294652 100644 --- a/src/Psalm/Plugin/EventHandler/Event/BeforeExpressionAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/BeforeExpressionAnalysisEvent.php @@ -12,15 +12,6 @@ final class BeforeExpressionAnalysisEvent { - private Expr $expr; - private Context $context; - private StatementsSource $statements_source; - private Codebase $codebase; - /** - * @var list - */ - private array $file_replacements; - /** * Called before an expression is checked * @@ -28,17 +19,12 @@ final class BeforeExpressionAnalysisEvent * @internal */ public function __construct( - Expr $expr, - Context $context, - StatementsSource $statements_source, - Codebase $codebase, - array $file_replacements = [] + private readonly Expr $expr, + private readonly Context $context, + private readonly StatementsSource $statements_source, + private readonly Codebase $codebase, + private array $file_replacements = [], ) { - $this->expr = $expr; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; } public function getExpr(): Expr diff --git a/src/Psalm/Plugin/EventHandler/Event/BeforeFileAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/BeforeFileAnalysisEvent.php index e04305afea3..d5d6f6af6fb 100644 --- a/src/Psalm/Plugin/EventHandler/Event/BeforeFileAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/BeforeFileAnalysisEvent.php @@ -1,5 +1,7 @@ - */ - private array $stmts; - /** * Called before a file has been checked * @@ -26,17 +19,12 @@ final class BeforeFileAnalysisEvent * @internal */ public function __construct( - StatementsSource $statements_source, - Context $file_context, - FileStorage $file_storage, - Codebase $codebase, - array $stmts + private readonly StatementsSource $statements_source, + private readonly Context $file_context, + private readonly FileStorage $file_storage, + private readonly Codebase $codebase, + private readonly array $stmts, ) { - $this->statements_source = $statements_source; - $this->file_context = $file_context; - $this->file_storage = $file_storage; - $this->codebase = $codebase; - $this->stmts = $stmts; } public function getStatementsSource(): StatementsSource diff --git a/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php b/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php index ccd42151eb5..7792ec4668a 100644 --- a/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/BeforeStatementAnalysisEvent.php @@ -12,15 +12,6 @@ final class BeforeStatementAnalysisEvent { - private Stmt $stmt; - private Context $context; - private StatementsSource $statements_source; - private Codebase $codebase; - /** - * @var list - */ - private array $file_replacements; - /** * Called after a statement has been checked * @@ -28,17 +19,12 @@ final class BeforeStatementAnalysisEvent * @internal */ public function __construct( - Stmt $stmt, - Context $context, - StatementsSource $statements_source, - Codebase $codebase, - array $file_replacements = [] + private Stmt $stmt, + private readonly Context $context, + private readonly StatementsSource $statements_source, + private readonly Codebase $codebase, + private array $file_replacements = [], ) { - $this->stmt = $stmt; - $this->context = $context; - $this->statements_source = $statements_source; - $this->codebase = $codebase; - $this->file_replacements = $file_replacements; } public function getStmt(): Stmt diff --git a/src/Psalm/Plugin/EventHandler/Event/DynamicFunctionStorageProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/DynamicFunctionStorageProviderEvent.php index 639d2746e32..32246d49e96 100644 --- a/src/Psalm/Plugin/EventHandler/Event/DynamicFunctionStorageProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/DynamicFunctionStorageProviderEvent.php @@ -14,33 +14,18 @@ final class DynamicFunctionStorageProviderEvent { - private ArgTypeInferer $arg_type_inferer; - private DynamicTemplateProvider $template_provider; - private StatementsSource $statement_source; - private string $function_id; - private PhpParser\Node\Expr\FuncCall $func_call; - private Context $context; - private CodeLocation $code_location; - /** * @internal */ public function __construct( - ArgTypeInferer $arg_type_inferer, - DynamicTemplateProvider $template_provider, - StatementsSource $statements_source, - string $function_id, - PhpParser\Node\Expr\FuncCall $func_call, - Context $context, - CodeLocation $code_location + private readonly ArgTypeInferer $arg_type_inferer, + private readonly DynamicTemplateProvider $template_provider, + private readonly StatementsSource $statement_source, + private readonly string $function_id, + private readonly PhpParser\Node\Expr\FuncCall $func_call, + private readonly Context $context, + private readonly CodeLocation $code_location, ) { - $this->statement_source = $statements_source; - $this->function_id = $function_id; - $this->func_call = $func_call; - $this->context = $context; - $this->code_location = $code_location; - $this->arg_type_inferer = $arg_type_inferer; - $this->template_provider = $template_provider; } public function getArgTypeInferer(): ArgTypeInferer diff --git a/src/Psalm/Plugin/EventHandler/Event/FunctionExistenceProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/FunctionExistenceProviderEvent.php index 3edaf09322e..77448542420 100644 --- a/src/Psalm/Plugin/EventHandler/Event/FunctionExistenceProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/FunctionExistenceProviderEvent.php @@ -1,14 +1,13 @@ statements_source = $statements_source; - $this->function_id = $function_id; } public function getStatementsSource(): StatementsSource diff --git a/src/Psalm/Plugin/EventHandler/Event/FunctionParamsProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/FunctionParamsProviderEvent.php index 6dd717c6413..46f1163f062 100644 --- a/src/Psalm/Plugin/EventHandler/Event/FunctionParamsProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/FunctionParamsProviderEvent.php @@ -1,5 +1,7 @@ - */ - private array $call_args; - private ?Context $context; - private ?CodeLocation $code_location; - /** * @param list $call_args * @internal */ public function __construct( - StatementsSource $statements_source, - string $function_id, - array $call_args, - ?Context $context = null, - ?CodeLocation $code_location = null + private readonly StatementsSource $statements_source, + private readonly string $function_id, + private readonly array $call_args, + private readonly ?Context $context = null, + private readonly ?CodeLocation $code_location = null, ) { - $this->statements_source = $statements_source; - $this->function_id = $function_id; - $this->call_args = $call_args; - $this->context = $context; - $this->code_location = $code_location; } public function getStatementsSource(): StatementsSource diff --git a/src/Psalm/Plugin/EventHandler/Event/FunctionReturnTypeProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/FunctionReturnTypeProviderEvent.php index c620637bb4d..4b108f6d3f8 100644 --- a/src/Psalm/Plugin/EventHandler/Event/FunctionReturnTypeProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/FunctionReturnTypeProviderEvent.php @@ -1,5 +1,7 @@ statements_source = $statements_source; - $this->function_id = $function_id; - $this->stmt = $stmt; - $this->context = $context; - $this->code_location = $code_location; } public function getStatementsSource(): StatementsSource diff --git a/src/Psalm/Plugin/EventHandler/Event/MethodExistenceProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/MethodExistenceProviderEvent.php index ff1fb8f48e5..5d87ecba720 100644 --- a/src/Psalm/Plugin/EventHandler/Event/MethodExistenceProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/MethodExistenceProviderEvent.php @@ -1,5 +1,7 @@ fq_classlike_name = $fq_classlike_name; - $this->method_name_lowercase = $method_name_lowercase; - $this->source = $source; - $this->code_location = $code_location; } public function getFqClasslikeName(): string diff --git a/src/Psalm/Plugin/EventHandler/Event/MethodParamsProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/MethodParamsProviderEvent.php index eb366963c66..bcf7d0256a1 100644 --- a/src/Psalm/Plugin/EventHandler/Event/MethodParamsProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/MethodParamsProviderEvent.php @@ -1,5 +1,7 @@ |null - */ - private ?array $call_args; - private ?StatementsSource $statements_source; - private ?Context $context; - private ?CodeLocation $code_location; - /** * @param list $call_args * @internal */ public function __construct( - string $fq_classlike_name, - string $method_name_lowercase, - ?array $call_args = null, - ?StatementsSource $statements_source = null, - ?Context $context = null, - ?CodeLocation $code_location = null + private readonly string $fq_classlike_name, + private readonly string $method_name_lowercase, + private readonly ?array $call_args = null, + private readonly ?StatementsSource $statements_source = null, + private readonly ?Context $context = null, + private readonly ?CodeLocation $code_location = null, ) { - $this->fq_classlike_name = $fq_classlike_name; - $this->method_name_lowercase = $method_name_lowercase; - $this->call_args = $call_args; - $this->statements_source = $statements_source; - $this->context = $context; - $this->code_location = $code_location; } public function getFqClasslikeName(): string diff --git a/src/Psalm/Plugin/EventHandler/Event/MethodReturnTypeProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/MethodReturnTypeProviderEvent.php index 8e4e58bced2..0338dec8a0c 100644 --- a/src/Psalm/Plugin/EventHandler/Event/MethodReturnTypeProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/MethodReturnTypeProviderEvent.php @@ -1,5 +1,7 @@ |null */ - private ?array $template_type_parameters; - private ?string $called_fq_classlike_name; - /** - * @var lowercase-string|null - */ - private ?string $called_method_name_lowercase; - /** * 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 PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $stmt * @param non-empty-list|null $template_type_parameters * @param lowercase-string $method_name_lowercase * @param lowercase-string $called_method_name_lowercase * @internal */ public function __construct( - StatementsSource $source, - string $fq_classlike_name, - string $method_name_lowercase, - $stmt, - Context $context, - CodeLocation $code_location, - ?array $template_type_parameters = null, - ?string $called_fq_classlike_name = null, - ?string $called_method_name_lowercase = null + private readonly StatementsSource $source, + private readonly string $fq_classlike_name, + private readonly string $method_name_lowercase, + private readonly PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall $stmt, + private readonly Context $context, + private readonly CodeLocation $code_location, + private readonly ?array $template_type_parameters = null, + private readonly ?string $called_fq_classlike_name = null, + private readonly ?string $called_method_name_lowercase = null, ) { - $this->source = $source; - $this->fq_classlike_name = $fq_classlike_name; - $this->method_name_lowercase = $method_name_lowercase; - $this->context = $context; - $this->code_location = $code_location; - $this->stmt = $stmt; - $this->template_type_parameters = $template_type_parameters; - $this->called_fq_classlike_name = $called_fq_classlike_name; - $this->called_method_name_lowercase = $called_method_name_lowercase; } public function getSource(): StatementsSource @@ -120,10 +92,7 @@ public function getCalledMethodNameLowercase(): ?string return $this->called_method_name_lowercase; } - /** - * @return PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall - */ - public function getStmt() + public function getStmt(): PhpParser\Node\Expr\MethodCall|PhpParser\Node\Expr\StaticCall { return $this->stmt; } diff --git a/src/Psalm/Plugin/EventHandler/Event/MethodVisibilityProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/MethodVisibilityProviderEvent.php index ffdf8e54b4f..17abcadd3b9 100644 --- a/src/Psalm/Plugin/EventHandler/Event/MethodVisibilityProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/MethodVisibilityProviderEvent.php @@ -1,5 +1,7 @@ source = $source; - $this->fq_classlike_name = $fq_classlike_name; - $this->method_name_lowercase = $method_name_lowercase; - $this->context = $context; - $this->code_location = $code_location; } public function getSource(): StatementsSource diff --git a/src/Psalm/Plugin/EventHandler/Event/PropertyExistenceProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/PropertyExistenceProviderEvent.php index 20681f01e61..a2df1d1774e 100644 --- a/src/Psalm/Plugin/EventHandler/Event/PropertyExistenceProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/PropertyExistenceProviderEvent.php @@ -1,5 +1,7 @@ fq_classlike_name = $fq_classlike_name; - $this->property_name = $property_name; - $this->read_mode = $read_mode; - $this->source = $source; - $this->context = $context; - $this->code_location = $code_location; } public function getFqClasslikeName(): string diff --git a/src/Psalm/Plugin/EventHandler/Event/PropertyTypeProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/PropertyTypeProviderEvent.php index 2f147607506..fd647070031 100644 --- a/src/Psalm/Plugin/EventHandler/Event/PropertyTypeProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/PropertyTypeProviderEvent.php @@ -1,5 +1,7 @@ fq_classlike_name = $fq_classlike_name; - $this->property_name = $property_name; - $this->read_mode = $read_mode; - $this->source = $source; - $this->context = $context; } public function getFqClasslikeName(): string diff --git a/src/Psalm/Plugin/EventHandler/Event/PropertyVisibilityProviderEvent.php b/src/Psalm/Plugin/EventHandler/Event/PropertyVisibilityProviderEvent.php index 8a99c821fc1..a566ec8c86c 100644 --- a/src/Psalm/Plugin/EventHandler/Event/PropertyVisibilityProviderEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/PropertyVisibilityProviderEvent.php @@ -1,5 +1,7 @@ source = $source; - $this->fq_classlike_name = $fq_classlike_name; - $this->property_name = $property_name; - $this->read_mode = $read_mode; - $this->context = $context; - $this->code_location = $code_location; } public function getSource(): StatementsSource diff --git a/src/Psalm/Plugin/EventHandler/Event/StringInterpreterEvent.php b/src/Psalm/Plugin/EventHandler/Event/StringInterpreterEvent.php index 8d612bf721c..2013ee4a55d 100644 --- a/src/Psalm/Plugin/EventHandler/Event/StringInterpreterEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/StringInterpreterEvent.php @@ -1,24 +1,23 @@ value = $value; - $this->codebase = $codebase; + public function __construct( + private readonly string $value, + private readonly Codebase $codebase, + ) { } public function getValue(): string diff --git a/src/Psalm/Plugin/EventHandler/FunctionExistenceProviderInterface.php b/src/Psalm/Plugin/EventHandler/FunctionExistenceProviderInterface.php index 5a04a71b90e..f7b74ab1d7f 100644 --- a/src/Psalm/Plugin/EventHandler/FunctionExistenceProviderInterface.php +++ b/src/Psalm/Plugin/EventHandler/FunctionExistenceProviderInterface.php @@ -1,5 +1,7 @@ getCodebase()->config; - /** - * Deprecated logic, in Psalm 6 just use $config->shepherd_endpoint - * '#' here is just a hack/marker to use a custom endpoint instead just a custom domain - * case 1: empty option (use https://shepherd.dev/hooks/psalm/) - * case 2: custom domain (/hooks/psalm should be appended) (use https://custom.domain/hooks/psalm) - * case 3: custom endpoint (/hooks/psalm should be appended) (use custom endpoint) - */ - if (substr_compare($config->shepherd_endpoint, '#', -1) === 0) { - $shepherd_endpoint = $config->shepherd_endpoint; - } else { - /** @psalm-suppress DeprecatedProperty, DeprecatedMethod */ - $shepherd_endpoint = self::buildShepherdUrlFromHost($config->shepherd_host); - } - - self::sendPayload($shepherd_endpoint, $rawPayload); - } - - /** - * @psalm-pure - * @deprecated Will be removed in Psalm 6 - */ - private static function buildShepherdUrlFromHost(string $host): string - { - if (parse_url($host, PHP_URL_SCHEME) === null) { - $host = 'https://' . $host; - } - - return $host . '/hooks/psalm'; + self::sendPayload($config->shepherd_endpoint, $rawPayload); } /** @@ -155,6 +128,7 @@ private static function sendPayload(string $endpoint, array $rawPayload): void // Prepare new cURL resource $ch = curl_init($endpoint); + assert($ch !== false); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); curl_setopt($ch, CURLINFO_HEADER_OUT, true); @@ -205,28 +179,6 @@ private static function sendPayload(string $endpoint, array $rawPayload): void fwrite(STDERR, $output); } - /** - * @param mixed $ch - * @psalm-pure - * @deprecated Will be removed in Psalm 6 - */ - public static function getCurlErrorMessage($ch): string - { - /** - * @psalm-suppress MixedArgument - * @var array - */ - $curl_info = curl_getinfo($ch); - - /** @psalm-suppress MixedAssignment */ - $ssl_verify_result = $curl_info['ssl_verify_result'] ?? null; - if (is_int($ssl_verify_result) && $ssl_verify_result > 1) { - return self::getCurlSslErrorMessage($ssl_verify_result); - } - - return ''; - } - /** * @psalm-pure */ diff --git a/src/Psalm/PluginFileExtensionsSocket.php b/src/Psalm/PluginFileExtensionsSocket.php index 0b5ac1333fa..bf8bc8f7876 100644 --- a/src/Psalm/PluginFileExtensionsSocket.php +++ b/src/Psalm/PluginFileExtensionsSocket.php @@ -1,5 +1,7 @@ > */ @@ -34,9 +34,9 @@ final class PluginFileExtensionsSocket implements FileExtensionsInterface /** * @internal */ - public function __construct(Config $config) - { - $this->config = $config; + public function __construct( + private readonly Config $config, + ) { } /** diff --git a/src/Psalm/PluginRegistrationSocket.php b/src/Psalm/PluginRegistrationSocket.php index aebfbe9605f..9523d21d8df 100644 --- a/src/Psalm/PluginRegistrationSocket.php +++ b/src/Psalm/PluginRegistrationSocket.php @@ -1,5 +1,7 @@ config = $config; - $this->codebase = $codebase; + public function __construct( + private readonly Config $config, + private readonly Codebase $codebase, + ) { } public function addStubFile(string $file_name): void diff --git a/src/Psalm/Progress/DebugProgress.php b/src/Psalm/Progress/DebugProgress.php index 5e5c1cbda80..2bfe8327466 100644 --- a/src/Psalm/Progress/DebugProgress.php +++ b/src/Psalm/Progress/DebugProgress.php @@ -1,5 +1,7 @@ write('Scanning files...' . "\n"); + $this->write("\n" . 'Scanning files...' . "\n\n"); } public function startAnalyzingFiles(): void { - $this->write('Analyzing files...' . "\n"); + $this->write("\n" . 'Analyzing files...' . "\n"); } public function startAlteringFiles(): void diff --git a/src/Psalm/Progress/DefaultProgress.php b/src/Psalm/Progress/DefaultProgress.php index f00717c1c58..64788f224cd 100644 --- a/src/Psalm/Progress/DefaultProgress.php +++ b/src/Psalm/Progress/DefaultProgress.php @@ -1,5 +1,7 @@ number_of_tasks > self::TOO_MANY_FILES) { + if ($this->fixed_size && $this->number_of_tasks > self::TOO_MANY_FILES) { ++$this->progress; // Source for rate limiting: diff --git a/src/Psalm/Progress/LongProgress.php b/src/Psalm/Progress/LongProgress.php index 6600d7ce55c..707f0f8f9ed 100644 --- a/src/Psalm/Progress/LongProgress.php +++ b/src/Psalm/Progress/LongProgress.php @@ -1,5 +1,7 @@ print_errors = $print_errors; - $this->print_infos = $print_infos; + public function __construct( + protected bool $print_errors = true, + protected bool $print_infos = true, + protected bool $in_ci = false, + ) { } public function startScanningFiles(): void { - $this->write('Scanning files...' . "\n"); + $this->fixed_size = false; + $this->write("\n" . 'Scanning files...' . ($this->in_ci ? '' : "\n\n")); } public function startAnalyzingFiles(): void { - $this->write('Analyzing files...' . "\n\n"); + $this->fixed_size = true; + $this->write("\n\n" . 'Analyzing files...' . "\n\n"); } public function startAlteringFiles(): void { + $this->fixed_size = true; $this->write('Altering files...' . "\n"); } @@ -59,8 +59,33 @@ public function start(int $number_of_tasks): void $this->progress = 0; } + public function expand(int $number_of_tasks): void + { + $this->number_of_tasks += $number_of_tasks; + } + public function taskDone(int $level): void { + if ($this->number_of_tasks === null) { + throw new LogicException('Progress::start() should be called before Progress::taskDone()'); + } + + ++$this->progress; + + if (!$this->fixed_size) { + if ($this->in_ci) { + return; + } + if ($this->progress == 1 || $this->progress == $this->number_of_tasks || $this->progress % 10 == 0) { + $this->write(sprintf( + "\r%s / %s...", + $this->progress, + $this->number_of_tasks, + )); + } + return; + } + if ($level === 0 || ($level === 1 && !$this->print_infos) || !$this->print_errors) { $this->write(self::doesTerminalSupportUtf8() ? '░' : '_'); } elseif ($level === 1) { @@ -69,7 +94,6 @@ public function taskDone(int $level): void $this->write('E'); } - ++$this->progress; if (($this->progress % self::NUMBER_OF_COLUMNS) !== 0) { return; diff --git a/src/Psalm/Progress/Progress.php b/src/Psalm/Progress/Progress.php index 709d04bdb67..248878ff0a1 100644 --- a/src/Psalm/Progress/Progress.php +++ b/src/Psalm/Progress/Progress.php @@ -1,5 +1,7 @@ */ - protected $issues_data; - - /** @var array */ - protected $fixable_issue_counts; - - /** @var bool */ - protected $use_color; + protected array $issues_data; - /** @var bool */ - protected $show_snippet; + protected bool $use_color; - /** @var bool */ - protected $show_info; + protected bool $show_snippet; - /** @var bool */ - protected $pretty; + protected bool $show_info; - /** @var bool */ - protected $in_ci; + protected bool $pretty; - /** @var int */ - protected $mixed_expression_count; - - /** @var int */ - protected $total_expression_count; + protected bool $in_ci; /** * @param array $issues_data @@ -66,10 +54,10 @@ abstract class Report */ public function __construct( array $issues_data, - array $fixable_issue_counts, + protected array $fixable_issue_counts, ReportOptions $report_options, - int $mixed_expression_count = 1, - int $total_expression_count = 1 + protected int $mixed_expression_count = 1, + protected int $total_expression_count = 1, ) { if (!$report_options->show_info) { $this->issues_data = array_filter( @@ -79,16 +67,12 @@ public function __construct( } else { $this->issues_data = $issues_data; } - $this->fixable_issue_counts = $fixable_issue_counts; $this->use_color = $report_options->use_color; $this->show_snippet = $report_options->show_snippet; $this->show_info = $report_options->show_info; $this->pretty = $report_options->pretty; $this->in_ci = $report_options->in_ci; - - $this->mixed_expression_count = $mixed_expression_count; - $this->total_expression_count = $total_expression_count; } protected function xmlEncode(string $data): string diff --git a/src/Psalm/Report/ByIssueLevelAndTypeReport.php b/src/Psalm/Report/ByIssueLevelAndTypeReport.php index 36aeb60221b..35f21b36dcc 100644 --- a/src/Psalm/Report/ByIssueLevelAndTypeReport.php +++ b/src/Psalm/Report/ByIssueLevelAndTypeReport.php @@ -1,5 +1,7 @@ file_name . ':' . $data->line_from . ':' . $data->column_from; @@ -166,8 +166,9 @@ private function getFileReference($data): string if (null === $this->link_format) { // if xdebug is not enabled, use `get_cfg_var` to get the value directly from php.ini - $this->link_format = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') - ?: 'file://%f#L%l'; + $this->link_format = ( + ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') + ) ?: 'file://%f#L%l'; } $link = strtr($this->link_format, ['%f' => $data->file_path, '%l' => $data->line_from]); diff --git a/src/Psalm/Report/CheckstyleReport.php b/src/Psalm/Report/CheckstyleReport.php index d862923b663..52de1a73b0c 100644 --- a/src/Psalm/Report/CheckstyleReport.php +++ b/src/Psalm/Report/CheckstyleReport.php @@ -1,5 +1,7 @@ pretty ? Json::PRETTY : Json::DEFAULT; $issues_data = array_map( - [$this, 'mapToNewStructure'], + $this->mapToNewStructure(...), $this->issues_data, ); @@ -37,7 +39,7 @@ public function create(): string * convert our own severity to CodeClimate format * Values can be : info, minor, major, critical, or blocker */ - protected function convertSeverity(string $input): string + private function convertSeverity(string $input): string { if (Config::REPORT_INFO === $input) { return 'info'; @@ -56,7 +58,7 @@ protected function convertSeverity(string $input): string /** * calculate a unique fingerprint for a given issue */ - protected function calculateFingerprint(IssueData $issue): string + private function calculateFingerprint(IssueData $issue): string { return md5($issue->type.$issue->message.$issue->file_name.$issue->from.$issue->to); } diff --git a/src/Psalm/Report/CompactReport.php b/src/Psalm/Report/CompactReport.php index 11416eb2840..094a385a033 100644 --- a/src/Psalm/Report/CompactReport.php +++ b/src/Psalm/Report/CompactReport.php @@ -1,5 +1,7 @@ file_name . ':' . $data->line_from . ':' . $data->column_from; @@ -135,8 +134,9 @@ private function getFileReference($data): string if (null === $this->link_format) { // if xdebug is not enabled, use `get_cfg_var` to get the value directly from php.ini - $this->link_format = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') - ?: 'file://%f#L%l'; + $this->link_format = ( + ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') + ) ?: 'file://%f#L%l'; } $link = strtr($this->link_format, ['%f' => $data->file_path, '%l' => $data->line_from]); diff --git a/src/Psalm/Report/CountReport.php b/src/Psalm/Report/CountReport.php index 4321789d44b..eb47b4c884b 100644 --- a/src/Psalm/Report/CountReport.php +++ b/src/Psalm/Report/CountReport.php @@ -9,7 +9,7 @@ use function array_key_exists; use function uksort; -class CountReport extends Report +final class CountReport extends Report { public function create(): string { diff --git a/src/Psalm/Report/EmacsReport.php b/src/Psalm/Report/EmacsReport.php index c1cb008e61d..256d1082e46 100644 --- a/src/Psalm/Report/EmacsReport.php +++ b/src/Psalm/Report/EmacsReport.php @@ -1,5 +1,7 @@ [ 'tags' => [ - (strpos($issue_data->type, 'Tainted') === 0) ? 'security' : 'maintainability', + (str_starts_with($issue_data->type, 'Tainted')) ? 'security' : 'maintainability', ], ], 'helpUri' => $issue_data->link, diff --git a/src/Psalm/Report/SonarqubeReport.php b/src/Psalm/Report/SonarqubeReport.php index 677751a69de..7e153c0cf7d 100644 --- a/src/Psalm/Report/SonarqubeReport.php +++ b/src/Psalm/Report/SonarqubeReport.php @@ -1,5 +1,7 @@ branch = $branch; - $this->head = $head; - $this->remotes = $remotes; + public function __construct( + protected string $branch, + protected CommitInfo $head, + /** + * Remote. + */ + protected array $remotes, + ) { } public function toArray(): array diff --git a/src/Psalm/SourceControl/Git/RemoteInfo.php b/src/Psalm/SourceControl/Git/RemoteInfo.php index b96b2c68c2a..ebb551b2186 100644 --- a/src/Psalm/SourceControl/Git/RemoteInfo.php +++ b/src/Psalm/SourceControl/Git/RemoteInfo.php @@ -1,5 +1,7 @@ count = $count; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/DoesNotHaveExactCount.php b/src/Psalm/Storage/Assertion/DoesNotHaveExactCount.php index 349e1533de4..e557085cd3f 100644 --- a/src/Psalm/Storage/Assertion/DoesNotHaveExactCount.php +++ b/src/Psalm/Storage/Assertion/DoesNotHaveExactCount.php @@ -1,21 +1,21 @@ count = $count; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/DoesNotHaveMethod.php b/src/Psalm/Storage/Assertion/DoesNotHaveMethod.php index 64dc6fb4c95..4a6a891c46e 100644 --- a/src/Psalm/Storage/Assertion/DoesNotHaveMethod.php +++ b/src/Psalm/Storage/Assertion/DoesNotHaveMethod.php @@ -1,19 +1,20 @@ method = $method; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/Empty_.php b/src/Psalm/Storage/Assertion/Empty_.php index f7e14b675b7..53bca41fada 100644 --- a/src/Psalm/Storage/Assertion/Empty_.php +++ b/src/Psalm/Storage/Assertion/Empty_.php @@ -1,14 +1,18 @@ key = $key; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/HasAtLeastCount.php b/src/Psalm/Storage/Assertion/HasAtLeastCount.php index 3a30e1d5666..98581348f93 100644 --- a/src/Psalm/Storage/Assertion/HasAtLeastCount.php +++ b/src/Psalm/Storage/Assertion/HasAtLeastCount.php @@ -1,21 +1,21 @@ count = $count; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/HasExactCount.php b/src/Psalm/Storage/Assertion/HasExactCount.php index b76cfc6144e..8f28be407fb 100644 --- a/src/Psalm/Storage/Assertion/HasExactCount.php +++ b/src/Psalm/Storage/Assertion/HasExactCount.php @@ -1,21 +1,21 @@ count = $count; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/HasIntOrStringArrayAccess.php b/src/Psalm/Storage/Assertion/HasIntOrStringArrayAccess.php index 4bcafad2312..39be9c6d610 100644 --- a/src/Psalm/Storage/Assertion/HasIntOrStringArrayAccess.php +++ b/src/Psalm/Storage/Assertion/HasIntOrStringArrayAccess.php @@ -1,8 +1,11 @@ method = $method; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/HasStringArrayAccess.php b/src/Psalm/Storage/Assertion/HasStringArrayAccess.php index d4aeb63c6e8..3a8e65a2813 100644 --- a/src/Psalm/Storage/Assertion/HasStringArrayAccess.php +++ b/src/Psalm/Storage/Assertion/HasStringArrayAccess.php @@ -1,8 +1,11 @@ type = $type; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsAClass.php b/src/Psalm/Storage/Assertion/IsAClass.php index a03f1dfc9c8..905302771c0 100644 --- a/src/Psalm/Storage/Assertion/IsAClass.php +++ b/src/Psalm/Storage/Assertion/IsAClass.php @@ -1,8 +1,11 @@ type = $type; - $this->allow_string = $allow_string; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsClassEqual.php b/src/Psalm/Storage/Assertion/IsClassEqual.php index a7c92f2aab2..88e8d1527c8 100644 --- a/src/Psalm/Storage/Assertion/IsClassEqual.php +++ b/src/Psalm/Storage/Assertion/IsClassEqual.php @@ -1,19 +1,20 @@ type = $type; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsClassNotEqual.php b/src/Psalm/Storage/Assertion/IsClassNotEqual.php index ae4ed1329c7..d94fb6246ef 100644 --- a/src/Psalm/Storage/Assertion/IsClassNotEqual.php +++ b/src/Psalm/Storage/Assertion/IsClassNotEqual.php @@ -1,19 +1,20 @@ type = $type; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/IsCountable.php b/src/Psalm/Storage/Assertion/IsCountable.php index 3933c4a13dd..f1f213df5b2 100644 --- a/src/Psalm/Storage/Assertion/IsCountable.php +++ b/src/Psalm/Storage/Assertion/IsCountable.php @@ -1,14 +1,18 @@ value = $value; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php b/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php index 9fc81110d12..295ad5eb444 100644 --- a/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php +++ b/src/Psalm/Storage/Assertion/IsGreaterThanOrEqualTo.php @@ -1,19 +1,20 @@ value = $value; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/IsIdentical.php b/src/Psalm/Storage/Assertion/IsIdentical.php index 8730bda852d..86f5212407a 100644 --- a/src/Psalm/Storage/Assertion/IsIdentical.php +++ b/src/Psalm/Storage/Assertion/IsIdentical.php @@ -1,8 +1,11 @@ type = $type; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsIsset.php b/src/Psalm/Storage/Assertion/IsIsset.php index 219649181a1..01fc40467e7 100644 --- a/src/Psalm/Storage/Assertion/IsIsset.php +++ b/src/Psalm/Storage/Assertion/IsIsset.php @@ -1,14 +1,18 @@ value = $value; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php b/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php index 83d681e0bdf..2ef344bf3c1 100644 --- a/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php +++ b/src/Psalm/Storage/Assertion/IsLessThanOrEqualTo.php @@ -1,19 +1,20 @@ value = $value; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsLooselyEqual.php b/src/Psalm/Storage/Assertion/IsLooselyEqual.php index c0b23dbde7c..4fd2aa367fb 100644 --- a/src/Psalm/Storage/Assertion/IsLooselyEqual.php +++ b/src/Psalm/Storage/Assertion/IsLooselyEqual.php @@ -1,8 +1,11 @@ type = $type; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/IsNotAClass.php b/src/Psalm/Storage/Assertion/IsNotAClass.php index 2cd3f80e187..d710eb7eea7 100644 --- a/src/Psalm/Storage/Assertion/IsNotAClass.php +++ b/src/Psalm/Storage/Assertion/IsNotAClass.php @@ -1,8 +1,11 @@ type = $type; - $this->allow_string = $allow_string; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/IsNotCountable.php b/src/Psalm/Storage/Assertion/IsNotCountable.php index c76fe24e26e..bf9b4db9a04 100644 --- a/src/Psalm/Storage/Assertion/IsNotCountable.php +++ b/src/Psalm/Storage/Assertion/IsNotCountable.php @@ -1,19 +1,20 @@ is_negatable = $is_negatable; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/IsNotIdentical.php b/src/Psalm/Storage/Assertion/IsNotIdentical.php index 978ca956df6..22b6e7c02d1 100644 --- a/src/Psalm/Storage/Assertion/IsNotIdentical.php +++ b/src/Psalm/Storage/Assertion/IsNotIdentical.php @@ -1,8 +1,11 @@ type = $type; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/IsNotIsset.php b/src/Psalm/Storage/Assertion/IsNotIsset.php index d42486326e6..890b3fdb1e1 100644 --- a/src/Psalm/Storage/Assertion/IsNotIsset.php +++ b/src/Psalm/Storage/Assertion/IsNotIsset.php @@ -1,14 +1,18 @@ type = $type; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/IsNotType.php b/src/Psalm/Storage/Assertion/IsNotType.php index 24d1ee9c380..28a769ca26e 100644 --- a/src/Psalm/Storage/Assertion/IsNotType.php +++ b/src/Psalm/Storage/Assertion/IsNotType.php @@ -1,8 +1,11 @@ type = $type; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/IsType.php b/src/Psalm/Storage/Assertion/IsType.php index 501a5e06cca..0bc6c63c9a1 100644 --- a/src/Psalm/Storage/Assertion/IsType.php +++ b/src/Psalm/Storage/Assertion/IsType.php @@ -1,8 +1,11 @@ type = $type; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/NestedAssertions.php b/src/Psalm/Storage/Assertion/NestedAssertions.php index 20f56ba585f..7d6f405caa2 100644 --- a/src/Psalm/Storage/Assertion/NestedAssertions.php +++ b/src/Psalm/Storage/Assertion/NestedAssertions.php @@ -1,8 +1,11 @@ >> */ - public array $assertions; - + use UnserializeMemoryUsageSuppressionTrait; /** @param array>> $assertions */ - public function __construct(array $assertions) + public function __construct(public readonly array $assertions) { - $this->assertions = $assertions; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/NonEmpty.php b/src/Psalm/Storage/Assertion/NonEmpty.php index b45076d90c6..159da72e798 100644 --- a/src/Psalm/Storage/Assertion/NonEmpty.php +++ b/src/Psalm/Storage/Assertion/NonEmpty.php @@ -1,14 +1,18 @@ is_negatable = $is_negatable; } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/NotInArray.php b/src/Psalm/Storage/Assertion/NotInArray.php index 73c352d47dd..fd47839f84e 100644 --- a/src/Psalm/Storage/Assertion/NotInArray.php +++ b/src/Psalm/Storage/Assertion/NotInArray.php @@ -1,8 +1,11 @@ type = $type; + use UnserializeMemoryUsageSuppressionTrait; + public function __construct( + public readonly Union $type, + ) { } public function getNegation(): Assertion diff --git a/src/Psalm/Storage/Assertion/NotNestedAssertions.php b/src/Psalm/Storage/Assertion/NotNestedAssertions.php index 7f2d33564c3..acf8696b1ea 100644 --- a/src/Psalm/Storage/Assertion/NotNestedAssertions.php +++ b/src/Psalm/Storage/Assertion/NotNestedAssertions.php @@ -1,8 +1,11 @@ >> */ - public array $assertions; - + use UnserializeMemoryUsageSuppressionTrait; /** @param array>> $assertions */ - public function __construct(array $assertions) + public function __construct(public readonly array $assertions) { - $this->assertions = $assertions; } public function isNegation(): bool diff --git a/src/Psalm/Storage/Assertion/NotNonEmptyCountable.php b/src/Psalm/Storage/Assertion/NotNonEmptyCountable.php index 48fc743c373..b9eb20f26b5 100644 --- a/src/Psalm/Storage/Assertion/NotNonEmptyCountable.php +++ b/src/Psalm/Storage/Assertion/NotNonEmptyCountable.php @@ -1,14 +1,18 @@ name = $name; - $this->type = $type; - $this->location = $location; } } diff --git a/src/Psalm/Storage/AttributeStorage.php b/src/Psalm/Storage/AttributeStorage.php index 72bca00bb60..6d4f84e3cdd 100644 --- a/src/Psalm/Storage/AttributeStorage.php +++ b/src/Psalm/Storage/AttributeStorage.php @@ -1,50 +1,28 @@ - */ - public $args; - - /** - * @var CodeLocation - * @psalm-suppress PossiblyUnusedProperty part of public API - */ - public $location; - - /** - * @var CodeLocation - * @psalm-suppress PossiblyUnusedProperty part of public API - */ - public $name_location; /** * @param list $args */ public function __construct( - string $fq_class_name, - array $args, - CodeLocation $location, - CodeLocation $name_location + public readonly string $fq_class_name, + public readonly array $args, + public readonly CodeLocation $location, + public readonly CodeLocation $name_location, ) { - $this->fq_class_name = $fq_class_name; - $this->args = $args; - $this->location = $location; - $this->name_location = $name_location; } } diff --git a/src/Psalm/Storage/ClassConstantStorage.php b/src/Psalm/Storage/ClassConstantStorage.php index 072f80a0439..17cd3928b50 100644 --- a/src/Psalm/Storage/ClassConstantStorage.php +++ b/src/Psalm/Storage/ClassConstantStorage.php @@ -1,5 +1,7 @@ - */ - public array $attributes = []; - - /** - * @var array - */ - public array $suppressed_issues = []; - - public ?string $description; - /** * @param ClassLikeAnalyzer::VISIBILITY_* $visibility * @param list $attributes * @param array $suppressed_issues */ public function __construct( - ?Union $type, - ?Union $inferred_type, - int $visibility, - ?CodeLocation $location, - ?CodeLocation $type_location = null, - ?CodeLocation $stmt_location = null, - bool $deprecated = false, - bool $final = false, - ?UnresolvedConstantComponent $unresolved_node = null, - array $attributes = [], - array $suppressed_issues = [], - ?string $description = null + /** + * The type from an annotation, or the inferred type if no annotation exists. + */ + public ?Union $type, + /** + * The type inferred from the value. + */ + public ?Union $inferred_type, + public readonly int $visibility, + public readonly ?CodeLocation $location, + public readonly ?CodeLocation $type_location = null, + public readonly ?CodeLocation $stmt_location = null, + public readonly bool $deprecated = false, + public readonly bool $final = false, + public readonly ?UnresolvedConstantComponent $unresolved_node = null, + public readonly array $attributes = [], + public readonly array $suppressed_issues = [], + public readonly ?string $description = null, ) { - $this->visibility = $visibility; - $this->location = $location; - $this->type = $type; - $this->inferred_type = $inferred_type; - $this->type_location = $type_location; - $this->stmt_location = $stmt_location; - $this->deprecated = $deprecated; - $this->final = $final; - $this->unresolved_node = $unresolved_node; - $this->attributes = $attributes; - $this->suppressed_issues = $suppressed_issues; - $this->description = $description; } /** @@ -98,18 +55,11 @@ public function __construct( */ public function getHoverMarkdown(string $const): string { - switch ($this->visibility) { - case ClassLikeAnalyzer::VISIBILITY_PRIVATE: - $visibility_text = 'private'; - break; - - case ClassLikeAnalyzer::VISIBILITY_PROTECTED: - $visibility_text = 'protected'; - break; - - default: - $visibility_text = 'public'; - } + $visibility_text = match ($this->visibility) { + ClassLikeAnalyzer::VISIBILITY_PRIVATE => 'private', + ClassLikeAnalyzer::VISIBILITY_PROTECTED => 'protected', + default => 'public', + }; $value = ''; if ($this->type) { diff --git a/src/Psalm/Storage/ClassLikeStorage.php b/src/Psalm/Storage/ClassLikeStorage.php index 6aec220047d..04c5fc94e86 100644 --- a/src/Psalm/Storage/ClassLikeStorage.php +++ b/src/Psalm/Storage/ClassLikeStorage.php @@ -1,5 +1,7 @@ */ - public $constants = []; + public array $constants = []; /** * Aliases to help Psalm understand constant refs - * - * @var ?Aliases */ - public $aliases; + public ?Aliases $aliases = null; - /** - * @var bool - */ - public $populated = false; + public bool $populated = false; - /** - * @var bool - */ - public $stubbed = false; + public bool $stubbed = false; - /** - * @var bool - */ - public $deprecated = false; + public bool $deprecated = false; /** * @var list */ - public $internal = []; + public array $internal = []; /** * @var TTemplateParam[] */ - public $templatedMixins = []; + public array $templatedMixins = []; /** * @var list */ - public $namedMixins = []; + public array $namedMixins = []; - /** - * @var ?string - */ - public $mixin_declaring_fqcln; + public ?string $mixin_declaring_fqcln = null; - /** - * @var ?bool - */ - public $sealed_properties = null; + public ?bool $sealed_properties = null; - /** - * @var ?bool - */ - public $sealed_methods = null; + public ?bool $sealed_methods = null; - /** - * @var bool - */ - public $override_property_visibility = false; + public bool $override_property_visibility = false; - /** - * @var bool - */ - public $override_method_visibility = false; + public bool $override_method_visibility = false; /** * @var array */ - public $suppressed_issues = []; - - /** - * @var string - */ - public $name; + public array $suppressed_issues = []; /** * Is this class user-defined - * - * @var bool */ - public $user_defined = false; + public bool $user_defined = false; /** * Interfaces this class implements directly * * @var array */ - public $direct_class_interfaces = []; + public array $direct_class_interfaces = []; /** * Interfaces this class implements explicitly and implicitly * * @var array */ - public $class_implements = []; + public array $class_implements = []; /** * Parent interfaces listed explicitly * * @var array */ - public $direct_interface_parents = []; + public array $direct_interface_parents = []; /** * Parent interfaces * * @var array */ - public $parent_interfaces = []; + public array $parent_interfaces = []; /** * There can only be one direct parent class - * - * @var ?string */ - public $parent_class; + public ?string $parent_class = null; /** * Parent classes * * @var array */ - public $parent_classes = []; + public array $parent_classes = []; - /** - * @var CodeLocation|null - */ - public $location; + public ?CodeLocation $location = null; - /** - * @var CodeLocation|null - */ - public $stmt_location; + public ?CodeLocation $stmt_location = null; - /** - * @var CodeLocation|null - */ - public $namespace_name_location; + public ?CodeLocation $namespace_name_location = null; - /** - * @var bool - */ - public $abstract = false; + public bool $abstract = false; - /** - * @var bool - */ - public $final = false; + public bool $final = false; - /** - * @var bool - */ - public $final_from_docblock = false; + public bool $final_from_docblock = false; /** * @var array */ - public $used_traits = []; + public array $used_traits = []; /** * @var array */ - public $trait_alias_map = []; + public array $trait_alias_map = []; /** * @var array @@ -197,57 +146,39 @@ final class ClassLikeStorage implements HasAttributesInterface /** * @var array */ - public $trait_final_map = []; + public array $trait_final_map = []; /** * @var array */ - public $trait_visibility_map = []; + public array $trait_visibility_map = []; - /** - * @var bool - */ - public $is_trait = false; + public bool $is_trait = false; - /** - * @var bool - */ - public $is_interface = false; + public bool $is_interface = false; - /** - * @var bool - */ - public $is_enum = false; + public bool $is_enum = false; - /** - * @var bool - */ - public $external_mutation_free = false; + public bool $external_mutation_free = false; - /** - * @var bool - */ - public $mutation_free = false; + public bool $mutation_free = false; - /** - * @var bool - */ - public $specialize_instance = false; + public bool $specialize_instance = false; /** * @var array */ - public $methods = []; + public array $methods = []; /** * @var array */ - public $pseudo_methods = []; + public array $pseudo_methods = []; /** * @var array */ - public $pseudo_static_methods = []; + public array $pseudo_static_methods = []; /** * Maps pseudo method names to the original declaring method identifier @@ -257,17 +188,17 @@ final class ClassLikeStorage implements HasAttributesInterface * * @var array */ - public $declaring_pseudo_method_ids = []; + public array $declaring_pseudo_method_ids = []; /** * @var array */ - public $declaring_method_ids = []; + public array $declaring_method_ids = []; /** * @var array */ - public $appearing_method_ids = []; + public array $appearing_method_ids = []; /** * Map from lowercase method name to list of declarations in order from parent, to grandparent, to @@ -276,62 +207,59 @@ final class ClassLikeStorage implements HasAttributesInterface * * @var array> */ - public $overridden_method_ids = []; + public array $overridden_method_ids = []; /** * @var array */ - public $documenting_method_ids = []; + public array $documenting_method_ids = []; /** * @var array */ - public $inheritable_method_ids = []; + public array $inheritable_method_ids = []; /** * @var array> */ - public $potential_declaring_method_ids = []; + public array $potential_declaring_method_ids = []; /** * @var array */ - public $properties = []; + public array $properties = []; /** * @var array */ - public $pseudo_property_set_types = []; + public array $pseudo_property_set_types = []; /** * @var array */ - public $pseudo_property_get_types = []; + public array $pseudo_property_get_types = []; /** * @var array */ - public $declaring_property_ids = []; + public array $declaring_property_ids = []; /** * @var array */ - public $appearing_property_ids = []; + public array $appearing_property_ids = []; - /** - * @var ?Union - */ - public $inheritors = null; + public ?Union $inheritors = null; /** * @var array */ - public $inheritable_property_ids = []; + public array $inheritable_property_ids = []; /** * @var array> */ - public $overridden_property_ids = []; + public array $overridden_property_ids = []; /** * An array holding the class template "as" types. @@ -344,12 +272,12 @@ final class ClassLikeStorage implements HasAttributesInterface * * @var array>|null */ - public $template_types; + public ?array $template_types = null; /** * @var array|null */ - public $template_covariants; + public ?array $template_covariants = null; /** * A map of which generic classlikes are extended or implemented by this class or interface. @@ -359,7 +287,7 @@ final class ClassLikeStorage implements HasAttributesInterface * @internal * @var array>|null */ - public $template_extended_offsets; + public ?array $template_extended_offsets = null; /** * A map of which generic classlikes are extended or implemented by this class or interface. @@ -375,116 +303,94 @@ final class ClassLikeStorage implements HasAttributesInterface * * @var array>|null */ - public $template_extended_params; + public ?array $template_extended_params = null; /** * @var array|null */ - public $template_type_extends_count; + public ?array $template_type_extends_count = null; /** * @var array|null */ - public $template_type_implements_count; + public ?array $template_type_implements_count = null; - /** - * @var ?Union - */ - public $yield; + public ?Union $yield = null; - /** @var ?string */ - public $declaring_yield_fqcn; + public ?string $declaring_yield_fqcn = null; /** * @var array|null */ - public $template_type_uses_count; + public ?array $template_type_uses_count = null; /** * @var array */ - public $initialized_properties = []; + public array $initialized_properties = []; /** * @var array */ - public $invalid_dependencies = []; + public array $invalid_dependencies = []; /** * @var array */ - public $dependent_classlikes = []; + public array $dependent_classlikes = []; /** * A hash of the source file's name, contents, and this file's modified on date - * - * @var string */ - public $hash = ''; + public string $hash = ''; - /** - * @var bool - */ - public $has_visitor_issues = false; + public bool $has_visitor_issues = false; /** * @var list */ - public $docblock_issues = []; + public array $docblock_issues = []; /** * @var array */ - public $type_aliases = []; + public array $type_aliases = []; - /** - * @var bool - */ - public $preserve_constructor_signature = false; + public bool $preserve_constructor_signature = false; - /** - * @var bool - */ - public $enforce_template_inheritance = false; + public bool $enforce_template_inheritance = false; - /** - * @var null|string - */ - public $extension_requirement; + public ?string $extension_requirement = null; /** * @var array */ - public $implementation_requirements = []; + public array $implementation_requirements = []; /** * @var list */ - public $attributes = []; + public array $attributes = []; /** * @var array */ - public $enum_cases = []; + public array $enum_cases = []; /** * @var 'int'|'string'|null */ - public $enum_type; + public ?string $enum_type = null; - /** - * @var ?string - */ - public $description; + public ?string $description = null; public bool $public_api = false; public bool $readonly = false; - public function __construct(string $name) + public function __construct(public string $name) { - $this->name = $name; } /** @@ -497,7 +403,7 @@ public function getAttributeStorages(): array public function hasAttributeIncludingParents( string $fq_class_name, - Codebase $codebase + Codebase $codebase, ): bool { if ($this->hasAttribute($fq_class_name)) { return true; diff --git a/src/Psalm/Storage/CustomMetadataTrait.php b/src/Psalm/Storage/CustomMetadataTrait.php index 85a8610a690..e2679ee01c9 100644 --- a/src/Psalm/Storage/CustomMetadataTrait.php +++ b/src/Psalm/Storage/CustomMetadataTrait.php @@ -1,5 +1,7 @@ */ - public $custom_metadata = []; + public array $custom_metadata = []; } diff --git a/src/Psalm/Storage/EnumCaseStorage.php b/src/Psalm/Storage/EnumCaseStorage.php index ec0e6ee74b6..8da463ce2b3 100644 --- a/src/Psalm/Storage/EnumCaseStorage.php +++ b/src/Psalm/Storage/EnumCaseStorage.php @@ -1,5 +1,7 @@ value = $value; - $this->stmt_location = $location; } - /** @return int|string|null */ - public function getValue(ClassLikes $classlikes) + public function getValue(ClassLikes $classlikes): TLiteralInt|TLiteralString|null { $case_value = $this->value; @@ -49,11 +34,9 @@ public function getValue(ClassLikes $classlikes) $case_value, ); - if ($case_value instanceof TLiteralString) { - $case_value = $case_value->value; - } elseif ($case_value instanceof TLiteralInt) { - $case_value = $case_value->value; - } else { + if (!$case_value instanceof TLiteralString + && !$case_value instanceof TLiteralInt + ) { throw new UnexpectedValueException('Failed to infer case value'); } } diff --git a/src/Psalm/Storage/FileStorage.php b/src/Psalm/Storage/FileStorage.php index 043cd196a1a..132d5823f5a 100644 --- a/src/Psalm/Storage/FileStorage.php +++ b/src/Psalm/Storage/FileStorage.php @@ -1,5 +1,7 @@ */ - public $classlikes_in_file = []; + public array $classlikes_in_file = []; /** * @var array */ - public $referenced_classlikes = []; + public array $referenced_classlikes = []; /** * @var array */ - public $required_classes = []; + public array $required_classes = []; /** * @var array */ - public $required_interfaces = []; - - /** @var string */ - public $file_path; + public array $required_interfaces = []; /** * @var array */ - public $functions = []; + public array $functions = []; /** @var array */ - public $declaring_function_ids = []; + public array $declaring_function_ids = []; /** * @var array */ - public $constants = []; + public array $constants = []; /** @var array */ - public $declaring_constants = []; + public array $declaring_constants = []; /** @var array */ - public $required_file_paths = []; + public array $required_file_paths = []; /** @var array */ - public $required_by_file_paths = []; + public array $required_by_file_paths = []; - /** @var bool */ - public $populated = false; + public bool $populated = false; - /** @var bool */ - public $deep_scan = false; + public bool $deep_scan = false; - /** @var bool */ - public $has_extra_statements = false; + public bool $has_extra_statements = false; - /** - * @var string - */ - public $hash = ''; + public string $hash = ''; - /** - * @var bool - */ - public $has_visitor_issues = false; + public bool $has_visitor_issues = false; /** * @var list */ - public $docblock_issues = []; + public array $docblock_issues = []; /** * @var array */ - public $type_aliases = []; + public array $type_aliases = []; /** * @var array */ - public $classlike_aliases = []; + public array $classlike_aliases = []; - /** @var ?Aliases */ - public $aliases; + public ?Aliases $aliases = null; /** @var Aliases[] */ - public $namespace_aliases = []; + public array $namespace_aliases = []; - public function __construct(string $file_path) + public function __construct(public string $file_path) { - $this->file_path = $file_path; } } diff --git a/src/Psalm/Storage/FunctionLikeParameter.php b/src/Psalm/Storage/FunctionLikeParameter.php index 5078b15bbe8..2f2c143d565 100644 --- a/src/Psalm/Storage/FunctionLikeParameter.php +++ b/src/Psalm/Storage/FunctionLikeParameter.php @@ -1,5 +1,7 @@ |null */ - public $sinks; + public ?array $sinks = null; - /** - * @var bool - */ - public $assert_untainted = false; + public bool $assert_untainted = false; - /** - * @var bool - */ - public $type_inferred = false; + public bool $type_inferred = false; - /** - * @var bool - */ - public $expect_variable = false; + public bool $expect_variable = false; - /** - * @var bool - */ - public $promoted_property = false; + public bool $promoted_property = false; /** * @var list */ - public $attributes = []; + public array $attributes = []; - /** - * @var ?string - */ - public $description; + public ?string $description = null; /** * @psalm-external-mutation-free - * @param Union|UnresolvedConstantComponent|null $default_type + * @param string $name parameter name, without the "$" prefix */ public function __construct( - string $name, - bool $by_ref, - ?Union $type = null, - ?Union $signature_type = null, - ?CodeLocation $location = null, - ?CodeLocation $type_location = null, - bool $is_optional = true, - bool $is_nullable = false, - bool $is_variadic = false, - $default_type = null, - ?Union $out_type = null + public string $name, + public bool $by_ref, + public ?Union $type = null, + public ?Union $signature_type = null, + public ?CodeLocation $location = null, + public ?CodeLocation $type_location = null, + public bool $is_optional = true, + public bool $is_nullable = false, + public bool $is_variadic = false, + public Union|UnresolvedConstantComponent|null $default_type = null, + public ?Union $out_type = null, ) { - $this->name = $name; - $this->by_ref = $by_ref; - $this->type = $type; - $this->signature_type = $signature_type; - $this->is_optional = $is_optional; - $this->is_nullable = $is_nullable; - $this->is_variadic = $is_variadic; - $this->location = $location; - $this->type_location = $type_location; $this->signature_type_location = $type_location; - $this->default_type = $default_type; - $this->out_type = $out_type; } /** @psalm-mutation-free */ diff --git a/src/Psalm/Storage/FunctionLikeStorage.php b/src/Psalm/Storage/FunctionLikeStorage.php index 01ab5d13242..f4f7d37f341 100644 --- a/src/Psalm/Storage/FunctionLikeStorage.php +++ b/src/Psalm/Storage/FunctionLikeStorage.php @@ -1,11 +1,14 @@ */ - public $params = []; + public array $params = []; /** * @psalm-readonly-allow-private-mutation * @var array */ - public $param_lookup = []; + public array $param_lookup = []; - /** - * @var Union|null - */ - public $return_type; + public ?Union $return_type = null; - /** - * @var CodeLocation|null - */ - public $return_type_location; + public ?CodeLocation $return_type_location = null; - /** - * @var Union|null - */ - public $signature_return_type; + public ?Union $signature_return_type = null; - /** - * @var CodeLocation|null - */ - public $signature_return_type_location; + public ?CodeLocation $signature_return_type_location = null; - /** - * @var ?string - */ - public $cased_name; + public ?string $cased_name = null; /** * @var array */ - public $suppressed_issues = []; + public array $suppressed_issues = []; - /** - * @var ?bool - */ - public $deprecated; + public ?bool $deprecated = null; /** * @var list */ - public $internal = []; + public array $internal = []; - /** - * @var bool - */ - public $variadic = false; + public bool $variadic = false; - /** - * @var bool - */ - public $returns_by_ref = false; + public bool $returns_by_ref = false; - /** - * @var ?int - */ - public $required_param_count; + public ?int $required_param_count = null; /** * @var array */ - public $defined_constants = []; + public array $defined_constants = []; /** * @var array */ - public $global_variables = []; + public array $global_variables = []; /** * @var array */ - public $global_types = []; + public array $global_types = []; /** * An array holding the class template "as" types. @@ -121,64 +91,45 @@ abstract class FunctionLikeStorage implements HasAttributesInterface * * @var array>|null */ - public $template_types; + public ?array $template_types = null; /** * @var array */ - public $assertions = []; + public array $assertions = []; /** * @var array */ - public $if_true_assertions = []; + public array $if_true_assertions = []; /** * @var array */ - public $if_false_assertions = []; + public array $if_false_assertions = []; - /** - * @var bool - */ - public $has_visitor_issues = false; + public bool $has_visitor_issues = false; /** * @var list */ - public $docblock_issues = []; + public array $docblock_issues = []; /** * @var array */ - public $throws = []; + public array $throws = []; /** * @var array */ - public $throw_locations = []; - - /** - * @var bool - */ - public $has_yield = false; + public array $throw_locations = []; - /** - * @var bool - */ - public $mutation_free = false; + public bool $has_yield = false; - /** - * @var string|null - */ - public $return_type_description; + public bool $mutation_free = false; - /** - * @psalm-suppress PossiblyUnusedProperty - * @var array|null - * @deprecated will be removed in Psalm 6. use {@see FunctionLikeStorage::$unused_docblock_parameters} instead - */ - public $unused_docblock_params; + public ?string $return_type_description = null; /** * @var array @@ -187,63 +138,54 @@ abstract class FunctionLikeStorage implements HasAttributesInterface public bool $has_undertyped_native_parameters = false; - /** - * @var bool - */ - public $pure = false; + public bool $is_static = false; + + public bool $pure = false; /** * Whether or not the function output is dependent solely on input - a function can be * impure but still have this property (e.g. var_export). Useful for taint analysis. - * - * @var bool */ - public $specialize_call = false; + public bool $specialize_call = false; /** * @var array */ - public $taint_source_types = []; + public array $taint_source_types = []; /** * @var array */ - public $added_taints = []; + public array $added_taints = []; /** * @var array */ - public $removed_taints = []; + public array $removed_taints = []; /** * @var array */ - public $conditionally_removed_taints = []; + public array $conditionally_removed_taints = []; /** * @var array */ - public $return_source_params = []; + public array $return_source_params = []; - /** - * @var bool - */ - public $allow_named_arg_calls = true; + public bool $allow_named_arg_calls = true; /** * @var list */ - public $attributes = []; + public array $attributes = []; /** * @var list, return: bool}>|null */ - public $proxy_calls = []; + public ?array $proxy_calls = []; - /** - * @var ?string - */ - public $description; + public ?string $description = null; public bool $public_api = false; @@ -269,18 +211,11 @@ static function (FunctionLikeParameter $param): string { return $symbol_text; } - switch ($this->visibility) { - case ClassLikeAnalyzer::VISIBILITY_PRIVATE: - $visibility_text = 'private'; - break; - - case ClassLikeAnalyzer::VISIBILITY_PROTECTED: - $visibility_text = 'protected'; - break; - - default: - $visibility_text = 'public'; - } + $visibility_text = match ($this->visibility) { + ClassLikeAnalyzer::VISIBILITY_PRIVATE => 'private', + ClassLikeAnalyzer::VISIBILITY_PROTECTED => 'protected', + default => 'public', + }; return $visibility_text . ' ' . $symbol_text; } @@ -299,18 +234,11 @@ public function getCompletionSignature(): string return $symbol_text; } - switch ($this->visibility) { - case ClassLikeAnalyzer::VISIBILITY_PRIVATE: - $visibility_text = 'private'; - break; - - case ClassLikeAnalyzer::VISIBILITY_PROTECTED: - $visibility_text = 'protected'; - break; - - default: - $visibility_text = 'public'; - } + $visibility_text = match ($this->visibility) { + ClassLikeAnalyzer::VISIBILITY_PRIVATE => 'private', + ClassLikeAnalyzer::VISIBILITY_PROTECTED => 'protected', + default => 'public', + }; return $visibility_text . ' ' . $symbol_text; } @@ -347,13 +275,4 @@ public function __toString(): string { return $this->getCompletionSignature(); } - - /** - * @deprecated will be removed in Psalm 6. use {@see FunctionLikeStorage::getCompletionSignature()} instead - * @psalm-suppress PossiblyUnusedParam, PossiblyUnusedMethod - */ - public function getSignature(bool $allow_newlines): string - { - return $this->getCompletionSignature(); - } } diff --git a/src/Psalm/Storage/FunctionStorage.php b/src/Psalm/Storage/FunctionStorage.php index 813730f567a..043bb1832ba 100644 --- a/src/Psalm/Storage/FunctionStorage.php +++ b/src/Psalm/Storage/FunctionStorage.php @@ -1,15 +1,12 @@ */ - public $byref_uses = []; - - /** - * @var bool - * @todo lift this property to FunctionLikeStorage in Psalm 6 - */ - public $is_static = false; + public array $byref_uses = []; } diff --git a/src/Psalm/Storage/ImmutableNonCloneableTrait.php b/src/Psalm/Storage/ImmutableNonCloneableTrait.php index 7609a73a8a4..092d702fbb4 100644 --- a/src/Psalm/Storage/ImmutableNonCloneableTrait.php +++ b/src/Psalm/Storage/ImmutableNonCloneableTrait.php @@ -1,5 +1,7 @@ */ - public $this_property_mutations; + public ?array $this_property_mutations = null; - /** - * @var Union|null - */ - public $self_out_type; + public ?Union $self_out_type = null; - /** - * @var Union|null - */ - public $if_this_is_type = null; - /** - * @var bool - */ - public $stubbed = false; + public ?Union $if_this_is_type = null; + public bool $stubbed = false; - /** - * @var bool - */ - public $probably_fluent = false; + public bool $probably_fluent = false; } diff --git a/src/Psalm/Storage/Possibilities.php b/src/Psalm/Storage/Possibilities.php index 2c04ed415ed..d9ca584db4d 100644 --- a/src/Psalm/Storage/Possibilities.php +++ b/src/Psalm/Storage/Possibilities.php @@ -1,5 +1,7 @@ the rule being asserted - */ - public $rule; - - /** - * @var int|string the id of the property/variable, or - * the parameter offset of the affected arg - */ - public $var_id; - - /** - * @param string|int $var_id * @param list $rule */ - public function __construct($var_id, array $rule) - { - $this->rule = $rule; - $this->var_id = $var_id; + public function __construct( + /** + * @var int|string the id of the property/variable, or + * the parameter offset of the affected arg + */ + public int|string $var_id, + public array $rule, + ) { } public function getUntemplatedCopy( TemplateResult $template_result, ?string $this_var_id, - ?Codebase $codebase + ?Codebase $codebase, ): self { $assertion_rules = []; diff --git a/src/Psalm/Storage/PropertyStorage.php b/src/Psalm/Storage/PropertyStorage.php index c285c737ee8..c7427e58aa6 100644 --- a/src/Psalm/Storage/PropertyStorage.php +++ b/src/Psalm/Storage/PropertyStorage.php @@ -1,5 +1,7 @@ */ - public $internal = []; + public array $internal = []; - /** - * @var ?string - */ - public $getter_method; + public ?string $getter_method = null; - /** - * @var bool - */ - public $is_promoted = false; + public bool $is_promoted = false; /** * @var list */ - public $attributes = []; + public array $attributes = []; /** * @var array */ - public $suppressed_issues = []; + public array $suppressed_issues = []; - /** - * @var ?string - */ - public $description; + public ?string $description = null; public function getInfo(): string { - switch ($this->visibility) { - case ClassLikeAnalyzer::VISIBILITY_PRIVATE: - $visibility_text = 'private'; - break; - - case ClassLikeAnalyzer::VISIBILITY_PROTECTED: - $visibility_text = 'protected'; - break; - - default: - $visibility_text = 'public'; - } + $visibility_text = match ($this->visibility) { + ClassLikeAnalyzer::VISIBILITY_PRIVATE => 'private', + ClassLikeAnalyzer::VISIBILITY_PROTECTED => 'protected', + default => 'public', + }; return $visibility_text . ' ' . ($this->type ? $this->type->getId() : 'mixed'); } diff --git a/src/Psalm/Storage/UnserializeMemoryUsageSuppressionTrait.php b/src/Psalm/Storage/UnserializeMemoryUsageSuppressionTrait.php index a59b7ce2a02..f60e264da22 100644 --- a/src/Psalm/Storage/UnserializeMemoryUsageSuppressionTrait.php +++ b/src/Psalm/Storage/UnserializeMemoryUsageSuppressionTrait.php @@ -1,5 +1,7 @@ uses; - if (strpos($class, '\\') !== false) { + if (str_contains($class, '\\')) { $class_parts = explode('\\', $class); $first_namespace = array_shift($class_parts); @@ -128,7 +130,7 @@ public static function getStringFromFQCLN( array $aliased_classes, ?string $this_class, bool $allow_self = false, - bool $is_static = false + bool $is_static = false, ): string { if ($allow_self && $value === $this_class) { if ($is_static) { @@ -142,7 +144,7 @@ public static function getStringFromFQCLN( } if ($namespace && stripos($value, $namespace . '\\') === 0) { - $candidate = preg_replace( + $candidate = (string) preg_replace( '/^' . preg_quote($namespace . '\\') . '/i', '', $value, @@ -153,7 +155,7 @@ public static function getStringFromFQCLN( if (!isset($aliased_classes[strtolower($candidate_parts[0])])) { return $candidate; } - } elseif (!$namespace && strpos($value, '\\') === false) { + } elseif (!$namespace && !str_contains($value, '\\')) { return $value; } @@ -260,10 +262,10 @@ public static function getNumericString(): Union } /** - * @param int|string $value + * @psalm-suppress PossiblyUnusedMethod * @return TLiteralString|TLiteralInt */ - public static function getLiteral($value): Atomic + public static function getLiteral(int|string $value): Atomic { if (is_int($value)) { return new TLiteralInt($value); @@ -591,7 +593,7 @@ public static function combineUnionTypes( bool $overwrite_empty_array = false, bool $allow_mixed_union = true, int $literal_limit = 500, - ?bool $possibly_undefined = null + ?bool $possibly_undefined = null, ): Union { if ($type_2 === null && $type_1 === null) { throw new UnexpectedValueException('At least one type must be provided to combine'); @@ -628,13 +630,13 @@ public static function combineUnionTypes( $both_failed_reconciliation = true; } else { return $type_2->setProperties([ - 'parent_nodes' => array_merge($type_2->parent_nodes, $type_1->parent_nodes), + 'parent_nodes' => [...$type_2->parent_nodes, ...$type_1->parent_nodes], 'possibly_undefined' => $possibly_undefined ?? $type_2->possibly_undefined, ]); } } elseif ($type_2->failed_reconciliation) { return $type_1->setProperties([ - 'parent_nodes' => array_merge($type_1->parent_nodes, $type_2->parent_nodes), + 'parent_nodes' => [...$type_1->parent_nodes, ...$type_2->parent_nodes], 'possibly_undefined' => $possibly_undefined ?? $type_1->possibly_undefined, ]); } @@ -716,7 +718,7 @@ public static function intersectUnionTypes( ?Union $type_2, Codebase $codebase, bool $allow_interface_equality = false, - bool $allow_float_int_equality = true + bool $allow_float_int_equality = true, ): ?Union { if ($type_2 === null && $type_1 === null) { throw new UnexpectedValueException('At least one type must be provided to combine'); @@ -846,7 +848,7 @@ private static function intersectAtomicTypes( Codebase $codebase, bool &$intersection_performed, bool $allow_interface_equality = false, - bool $allow_float_int_equality = true + bool $allow_float_int_equality = true, ): ?Atomic { $intersection_atomic = null; $wider_type = null; @@ -854,15 +856,15 @@ private static function intersectAtomicTypes( && $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) + && $type_1_atomic::class === TNamedObject::class + && $type_2_atomic::class !== TNamedObject::class) ) { $intersection_atomic = $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) + && $type_2_atomic::class === TNamedObject::class + && $type_1_atomic::class !== TNamedObject::class) ) { $intersection_atomic = $type_1_atomic; $wider_type = $type_2_atomic; @@ -917,7 +919,7 @@ private static function intersectAtomicTypes( ) { return $intersection_atomic; } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // Ignore non-existing classes during initial scan } } @@ -935,7 +937,7 @@ private static function intersectAtomicTypes( if ($first_is_class && $second_is_class) { return $intersection_atomic; } - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // Ignore non-existing classes during initial scan } } @@ -997,7 +999,7 @@ private static function mayHaveIntersection(Atomic $type, Codebase $codebase): b } try { $storage = $codebase->classlike_storage_provider->get($type->value); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // Ignore non-existing classes during initial scan return true; } diff --git a/src/Psalm/Type/Atomic.php b/src/Psalm/Type/Atomic.php index c6a247d769d..ece578d3ccd 100644 --- a/src/Psalm/Type/Atomic.php +++ b/src/Psalm/Type/Atomic.php @@ -1,5 +1,7 @@ from_docblock = $from_docblock; + public function __construct( + /** + * Whether or not the type comes from a docblock + */ + public bool $from_docblock = false, + ) { } protected function __clone() { @@ -95,32 +98,14 @@ protected function __clone() /** * Whether or not the type has been checked yet - * - * @var bool */ - public $checked = false; + public bool $checked = false; - /** - * Whether or not the type comes from a docblock - * - * @var bool - */ - public $from_docblock = false; + public ?int $offset_start = null; - /** - * @var ?int - */ - public $offset_start; + public ?int $offset_end = null; - /** - * @var ?int - */ - public $offset_end; - - /** - * @var ?string - */ - public $text; + public ?string $text = null; /** * @return static @@ -163,7 +148,7 @@ public static function create( ?int $offset_start = null, ?int $offset_end = null, ?string $text = null, - bool $from_docblock = false + bool $from_docblock = false, ): Atomic { $result = self::createInner( $value, @@ -189,7 +174,7 @@ private static function createInner( ?int $analysis_php_version_id = null, array $template_type_map = [], array $type_aliases = [], - bool $from_docblock = false + bool $from_docblock = false, ): Atomic { switch ($value) { case 'int': @@ -263,9 +248,19 @@ private static function createInner( ]); case 'callable-array': - return new TCallableArray([ - new Union([new TArrayKey($from_docblock)]), - new Union([new TMixed(false, $from_docblock)]), + $classString = new TClassString( + 'object', + null, + false, + false, + false, + true, + ); + $object = new TObject(true); + $string = new TNonEmptyString(true); + return new TCallableKeyedArray([ + new Union([$classString, $object]), + new Union([$string]), ]); case 'list': @@ -392,7 +387,7 @@ private static function createInner( return new TClosure('Closure'); } - if (strpos($value, '-') && strpos($value, 'OCI-') !== 0) { + if (strpos($value, '-') && !str_starts_with($value, 'OCI-')) { throw new TypeParseTreeException('Unrecognized type ' . $value); } @@ -425,7 +420,7 @@ private static function createInner( /** * This is the string that will be used to represent the type in Union::$types. This means that two types sharing - * the same getKey value will override themselves in an Union + * the same getKey value will override themselves in a Union */ abstract public function getKey(bool $include_extra = true): string; @@ -464,8 +459,6 @@ public function isCallableType(): bool return $this instanceof TCallable || $this instanceof TCallableObject || $this instanceof TCallableString - || $this instanceof TCallableArray - || $this instanceof TCallableList || $this instanceof TCallableKeyedArray || $this instanceof TClosure; } @@ -475,7 +468,6 @@ public function isIterable(Codebase $codebase): bool return $this instanceof TIterable || $this->hasTraversableInterface($codebase) || $this instanceof TArray - || $this instanceof TList || $this instanceof TKeyedArray; } @@ -521,8 +513,7 @@ public function isCountable(Codebase $codebase): bool { return $this->hasCountableInterface($codebase) || $this instanceof TArray - || $this instanceof TKeyedArray - || $this instanceof TList; + || $this instanceof TKeyedArray; } /** @@ -726,7 +717,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return $this->getKey(); } @@ -741,7 +732,7 @@ abstract public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string; abstract public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool; @@ -759,7 +750,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { // do nothing return $this; @@ -770,7 +761,7 @@ public function replaceTemplateTypesWithStandins( */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { // do nothing return $this; @@ -778,7 +769,7 @@ public function replaceTemplateTypesWithArgTypes( public function equals(Atomic $other_type, bool $ensure_source_equality): bool { - return get_class($other_type) === get_class($this); + return $other_type::class === static::class; } public function isTruthy(): bool diff --git a/src/Psalm/Type/Atomic/CallableTrait.php b/src/Psalm/Type/Atomic/CallableTrait.php index 4ca12639b5e..6f1c38d1298 100644 --- a/src/Psalm/Type/Atomic/CallableTrait.php +++ b/src/Psalm/Type/Atomic/CallableTrait.php @@ -1,5 +1,7 @@ |null */ - public $params = []; + public ?array $params = []; - /** - * @var Union|null - */ - public $return_type; + public ?Union $return_type = null; - /** - * @var ?bool - */ - public $is_pure; + public ?bool $is_pure = null; /** * Constructs a new instance of a generic type @@ -45,7 +41,7 @@ public function __construct( ?array $params = null, ?Union $return_type = null, ?bool $is_pure = null, - bool $from_docblock = false + bool $from_docblock = false, ) { $this->value = $value; $this->params = $params; @@ -127,7 +123,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { if ($this instanceof TNamedObject) { @@ -182,7 +178,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { if ($this instanceof TNamedObject) { return parent::toNamespacedString($namespace, $aliased_classes, $this_class, true); @@ -232,7 +228,7 @@ protected function replaceCallableTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): ?array { $replaced = false; $params = $this->params; @@ -300,7 +296,7 @@ protected function replaceCallableTemplateTypesWithStandins( */ protected function replaceCallableTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): ?array { $replaced = false; diff --git a/src/Psalm/Type/Atomic/DependentType.php b/src/Psalm/Type/Atomic/DependentType.php index 98cf7a5749c..ac4c953509b 100644 --- a/src/Psalm/Type/Atomic/DependentType.php +++ b/src/Psalm/Type/Atomic/DependentType.php @@ -1,5 +1,7 @@ getKeyedArray(); - } + $input_object_type_params = []; @@ -240,7 +239,7 @@ protected function replaceTypeParamsTemplateTypesWithStandins( */ protected function replaceTypeParamsTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): ?array { $type_params = $this->type_params; foreach ($type_params as $offset => $type_param) { diff --git a/src/Psalm/Type/Atomic/HasIntersectionTrait.php b/src/Psalm/Type/Atomic/HasIntersectionTrait.php index 9a8bc2864e2..22d07725fcd 100644 --- a/src/Psalm/Type/Atomic/HasIntersectionTrait.php +++ b/src/Psalm/Type/Atomic/HasIntersectionTrait.php @@ -1,5 +1,7 @@ extra_types) { return ''; @@ -91,7 +93,7 @@ public function getIntersectionTypes(): array */ protected function replaceIntersectionTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): ?array { if (!$this->extra_types) { return null; @@ -137,7 +139,7 @@ protected function replaceIntersectionTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): ?array { if (!$this->extra_types) { return null; diff --git a/src/Psalm/Type/Atomic/Scalar.php b/src/Psalm/Type/Atomic/Scalar.php index 764b34af86a..c4597dffbe7 100644 --- a/src/Psalm/Type/Atomic/Scalar.php +++ b/src/Psalm/Type/Atomic/Scalar.php @@ -1,7 +1,10 @@ $extra_types @@ -21,19 +18,17 @@ final class TAnonymousClassInstance extends TNamedObject public function __construct( string $value, bool $is_static = false, - ?string $extends = null, - array $extra_types = [] + public ?string $extends = null, + array $extra_types = [], ) { parent::__construct($value, $is_static, false, $extra_types); - - $this->extends = $extends; } public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return $analysis_php_version_id >= 7_02_00 ? ($this->extends ?? 'object') : null; } @@ -45,7 +40,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return $this->extends ?? 'object'; } diff --git a/src/Psalm/Type/Atomic/TArray.php b/src/Psalm/Type/Atomic/TArray.php index 54dfef34117..37cf0bc694f 100644 --- a/src/Psalm/Type/Atomic/TArray.php +++ b/src/Psalm/Type/Atomic/TArray.php @@ -1,15 +1,17 @@ `. It expects an array with two elements, both union types. @@ -18,6 +20,7 @@ */ class TArray extends Atomic { + use UnserializeMemoryUsageSuppressionTrait; /** * @use GenericTrait */ @@ -28,10 +31,7 @@ class TArray extends Atomic */ public array $type_params; - /** - * @var string - */ - public $value = 'array'; + public string $value = 'array'; /** * Constructs a new instance of a generic type @@ -56,7 +56,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return $this->getKey(); } @@ -68,7 +68,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool public function equals(Atomic $other_type, bool $ensure_source_equality): bool { - if (get_class($other_type) !== static::class) { + if ($other_type::class !== static::class) { return false; } @@ -119,7 +119,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $type_params = $this->replaceTypeParamsTemplateTypesWithStandins( $template_result, diff --git a/src/Psalm/Type/Atomic/TArrayKey.php b/src/Psalm/Type/Atomic/TArrayKey.php index 07307cf25f9..b7cef140a6c 100644 --- a/src/Psalm/Type/Atomic/TArrayKey.php +++ b/src/Psalm/Type/Atomic/TArrayKey.php @@ -1,5 +1,7 @@ = 7_00_00 ? 'bool' : null; } diff --git a/src/Psalm/Type/Atomic/TCallable.php b/src/Psalm/Type/Atomic/TCallable.php index bcd925b2643..96a7150b927 100644 --- a/src/Psalm/Type/Atomic/TCallable.php +++ b/src/Psalm/Type/Atomic/TCallable.php @@ -1,11 +1,14 @@ value = $value; $this->params = $params; @@ -49,7 +50,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return 'callable'; } @@ -88,7 +89,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $replaced = $this->replaceCallableTemplateTypesWithStandins( $template_result, diff --git a/src/Psalm/Type/Atomic/TCallableArray.php b/src/Psalm/Type/Atomic/TCallableArray.php deleted file mode 100644 index 695a9d0e8b9..00000000000 --- a/src/Psalm/Type/Atomic/TCallableArray.php +++ /dev/null @@ -1,16 +0,0 @@ - $properties + * @param array{Union, Union}|null $fallback_params + * @param array $class_strings + */ + public function __construct( + array $properties, + ?array $class_strings = null, + ?array $fallback_params = null, + bool $from_docblock = false, + ) { + parent::__construct( + $properties, + $class_strings, + $fallback_params, + true, + $from_docblock, + ); + } } diff --git a/src/Psalm/Type/Atomic/TCallableList.php b/src/Psalm/Type/Atomic/TCallableList.php deleted file mode 100644 index 71111e05dbc..00000000000 --- a/src/Psalm/Type/Atomic/TCallableList.php +++ /dev/null @@ -1,46 +0,0 @@ -count && !$this->min_count) { - return new TKeyedArray( - [$this->type_param], - null, - [Type::getListKey(), $this->type_param], - true, - $this->from_docblock, - ); - } - if ($this->count) { - return new TCallableKeyedArray( - array_fill(0, $this->count, $this->type_param), - null, - null, - true, - $this->from_docblock, - ); - } - return new TCallableKeyedArray( - array_fill(0, $this->min_count, $this->type_param), - null, - [Type::getListKey(), $this->type_param], - true, - $this->from_docblock, - ); - } -} diff --git a/src/Psalm/Type/Atomic/TCallableObject.php b/src/Psalm/Type/Atomic/TCallableObject.php index 364e37c5f33..bee4271da81 100644 --- a/src/Psalm/Type/Atomic/TCallableObject.php +++ b/src/Psalm/Type/Atomic/TCallableObject.php @@ -1,5 +1,7 @@ callable = $callable; } public function getKey(bool $include_extra = true): string @@ -36,7 +35,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return $analysis_php_version_id >= 7_02_00 ? 'object' : null; } diff --git a/src/Psalm/Type/Atomic/TCallableString.php b/src/Psalm/Type/Atomic/TCallableString.php index 6d5616a9f36..496bb3fcb1b 100644 --- a/src/Psalm/Type/Atomic/TCallableString.php +++ b/src/Psalm/Type/Atomic/TCallableString.php @@ -1,5 +1,7 @@ fq_classlike_name = $fq_classlike_name; - $this->const_name = $const_name; + use UnserializeMemoryUsageSuppressionTrait; + public function __construct( + public string $fq_classlike_name, + public string $const_name, + bool $from_docblock = false, + ) { parent::__construct($from_docblock); } @@ -47,7 +46,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } @@ -64,7 +63,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($this->fq_classlike_name === 'static') { return 'static::' . $this->const_name; diff --git a/src/Psalm/Type/Atomic/TClassString.php b/src/Psalm/Type/Atomic/TClassString.php index 723f8f40ca9..5a384a3755a 100644 --- a/src/Psalm/Type/Atomic/TClassString.php +++ b/src/Psalm/Type/Atomic/TClassString.php @@ -1,5 +1,7 @@ as = $as; - $this->as_type = $as_type; - $this->is_loaded = $is_loaded; - $this->is_interface = $is_interface; - $this->is_enum = $is_enum; parent::__construct($from_docblock); } /** @@ -107,7 +88,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return 'string'; } @@ -119,7 +100,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($this->as === 'object') { return 'class-string'; @@ -133,7 +114,7 @@ public function toNamespacedString( ) . '>'; } - if (!$namespace && strpos($this->as, '\\') === false) { + if (!$namespace && !str_contains($this->as, '\\')) { return 'class-string<' . $this->as . '>'; } @@ -167,7 +148,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { if (!$this->as_type) { return $this; diff --git a/src/Psalm/Type/Atomic/TClassStringMap.php b/src/Psalm/Type/Atomic/TClassStringMap.php index 56b43ccf597..e64f0c60313 100644 --- a/src/Psalm/Type/Atomic/TClassStringMap.php +++ b/src/Psalm/Type/Atomic/TClassStringMap.php @@ -1,5 +1,7 @@ param_name = $param_name; - $this->as_type = $as_type; - $this->value_param = $value_param; parent::__construct($from_docblock); } @@ -68,7 +54,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { return (new TArray([Type::getString(), $this->value_param])) @@ -101,7 +87,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return 'array'; } @@ -130,17 +116,13 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $cloned = null; foreach ([Type::getString(), $this->value_param] as $offset => $type_param) { $input_type_param = null; - if ($input_type instanceof TList) { - $input_type = $input_type->getKeyedArray(); - } - if (($input_type instanceof TGenericObject || $input_type instanceof TIterable || $input_type instanceof TArray) @@ -188,7 +170,7 @@ public function replaceTemplateTypesWithStandins( */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $value_param = TemplateInferredTypeReplacer::replace( $this->value_param, @@ -212,7 +194,7 @@ protected function getChildNodeKeys(): array public function equals(Atomic $other_type, bool $ensure_source_equality): bool { - if (get_class($other_type) !== static::class) { + if ($other_type::class !== self::class) { return false; } diff --git a/src/Psalm/Type/Atomic/TClosedResource.php b/src/Psalm/Type/Atomic/TClosedResource.php index 06d419ff13c..ffdf3697f27 100644 --- a/src/Psalm/Type/Atomic/TClosedResource.php +++ b/src/Psalm/Type/Atomic/TClosedResource.php @@ -1,7 +1,10 @@ */ - public $byref_uses = []; - /** * @param list $params * @param array $byref_uses @@ -31,14 +30,13 @@ public function __construct( ?array $params = null, ?Union $return_type = null, ?bool $is_pure = null, - array $byref_uses = [], + public array $byref_uses = [], array $extra_types = [], - bool $from_docblock = false + bool $from_docblock = false, ) { $this->params = $params; $this->return_type = $return_type; $this->is_pure = $is_pure; - $this->byref_uses = $byref_uses; parent::__construct( $value, false, @@ -59,7 +57,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $replaced = $this->replaceCallableTemplateTypesWithArgTypes($template_result, $codebase); $intersection = $this->replaceIntersectionTemplateTypesWithArgTypes($template_result, $codebase); @@ -89,7 +87,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $replaced = $this->replaceCallableTemplateTypesWithStandins( $template_result, diff --git a/src/Psalm/Type/Atomic/TConditional.php b/src/Psalm/Type/Atomic/TConditional.php index 9d3bbc9222b..6509a574724 100644 --- a/src/Psalm/Type/Atomic/TConditional.php +++ b/src/Psalm/Type/Atomic/TConditional.php @@ -1,10 +1,13 @@ param_name = $param_name; - $this->defining_class = $defining_class; - $this->as_type = $as_type; - $this->conditional_type = $conditional_type; - $this->if_type = $if_type; - $this->else_type = $else_type; parent::__construct($from_docblock); } @@ -67,7 +35,7 @@ public function setTypes( ?Union $as_type, ?Union $conditional_type = null, ?Union $if_type = null, - ?Union $else_type = null + ?Union $else_type = null, ): self { $as_type ??= $this->as_type; $conditional_type ??= $this->conditional_type; @@ -117,7 +85,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } @@ -129,7 +97,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return ''; } @@ -149,7 +117,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $conditional = TemplateInferredTypeReplacer::replace( $this->conditional_type, diff --git a/src/Psalm/Type/Atomic/TDependentGetClass.php b/src/Psalm/Type/Atomic/TDependentGetClass.php index 168355daece..3e6ab07329f 100644 --- a/src/Psalm/Type/Atomic/TDependentGetClass.php +++ b/src/Psalm/Type/Atomic/TDependentGetClass.php @@ -1,5 +1,7 @@ typeof = $typeof; - $this->as_type = $as_type; parent::__construct(false); } diff --git a/src/Psalm/Type/Atomic/TDependentGetDebugType.php b/src/Psalm/Type/Atomic/TDependentGetDebugType.php index c71b9b71b20..cddb12cdc6d 100644 --- a/src/Psalm/Type/Atomic/TDependentGetDebugType.php +++ b/src/Psalm/Type/Atomic/TDependentGetDebugType.php @@ -1,5 +1,7 @@ typeof = $typeof; parent::__construct(false); } diff --git a/src/Psalm/Type/Atomic/TDependentGetType.php b/src/Psalm/Type/Atomic/TDependentGetType.php index ca1c4f08453..a0d64d950f0 100644 --- a/src/Psalm/Type/Atomic/TDependentGetType.php +++ b/src/Psalm/Type/Atomic/TDependentGetType.php @@ -1,5 +1,7 @@ typeof = $typeof; parent::__construct(false); } diff --git a/src/Psalm/Type/Atomic/TDependentListKey.php b/src/Psalm/Type/Atomic/TDependentListKey.php deleted file mode 100644 index 338136d84d9..00000000000 --- a/src/Psalm/Type/Atomic/TDependentListKey.php +++ /dev/null @@ -1,53 +0,0 @@ - $value) - * - * @deprecated Will be removed in Psalm v6, use TIntRange instead - * @psalm-immutable - */ -final class TDependentListKey extends TInt implements DependentType -{ - /** - * Used to hold information as to what list variable this refers to - * - * @var string - */ - public $var_id; - - /** - * @param string $var_id the variable id - */ - public function __construct(string $var_id) - { - $this->var_id = $var_id; - parent::__construct(false); - } - - public function getId(bool $exact = true, bool $nested = false): string - { - return 'list-key<' . $this->var_id . '>'; - } - - public function getVarId(): string - { - return $this->var_id; - } - - public function getAssertionString(): string - { - return 'int'; - } - - public function getReplacement(): TInt - { - return new TInt(); - } - - public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool - { - return false; - } -} diff --git a/src/Psalm/Type/Atomic/TEmptyMixed.php b/src/Psalm/Type/Atomic/TEmptyMixed.php index a14b31f5ddf..5dc2ff7eb31 100644 --- a/src/Psalm/Type/Atomic/TEmptyMixed.php +++ b/src/Psalm/Type/Atomic/TEmptyMixed.php @@ -1,5 +1,7 @@ case_name = $case_name; } public function getKey(bool $include_extra = true): string @@ -35,7 +30,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return $this->value; } @@ -52,7 +47,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return $this->value . '::' . $this->case_name; } diff --git a/src/Psalm/Type/Atomic/TFalse.php b/src/Psalm/Type/Atomic/TFalse.php index 03d1e27c05f..0bfc243ef44 100644 --- a/src/Psalm/Type/Atomic/TFalse.php +++ b/src/Psalm/Type/Atomic/TFalse.php @@ -1,5 +1,7 @@ = 7_00_00 ? 'float' : null; } diff --git a/src/Psalm/Type/Atomic/TGenericObject.php b/src/Psalm/Type/Atomic/TGenericObject.php index 415458de135..7f6e32ea77a 100644 --- a/src/Psalm/Type/Atomic/TGenericObject.php +++ b/src/Psalm/Type/Atomic/TGenericObject.php @@ -1,5 +1,7 @@ $type_params @@ -41,17 +40,17 @@ final class TGenericObject extends TNamedObject public function __construct( string $value, array $type_params, - bool $remapped_params = false, + /** @var bool if the parameters have been remapped to another class */ + public bool $remapped_params = false, bool $is_static = false, array $extra_types = [], - bool $from_docblock = false + bool $from_docblock = false, ) { if ($value[0] === '\\') { $value = substr($value, 1); } $this->type_params = $type_params; - $this->remapped_params = $remapped_params; parent::__construct( $value, $is_static, @@ -90,7 +89,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { $result = $this->toNamespacedString($namespace, $aliased_classes, $this_class, true); $intersection = strrpos($result, '&'); @@ -142,7 +141,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $types = $this->replaceTypeParamsTemplateTypesWithStandins( $template_result, diff --git a/src/Psalm/Type/Atomic/TInt.php b/src/Psalm/Type/Atomic/TInt.php index 9cfd0de654d..5d503f02ac9 100644 --- a/src/Psalm/Type/Atomic/TInt.php +++ b/src/Psalm/Type/Atomic/TInt.php @@ -1,5 +1,7 @@ = 7_00_00 ? 'int' : null; } diff --git a/src/Psalm/Type/Atomic/TIntMask.php b/src/Psalm/Type/Atomic/TIntMask.php index e6e57539022..6e753372c1f 100644 --- a/src/Psalm/Type/Atomic/TIntMask.php +++ b/src/Psalm/Type/Atomic/TIntMask.php @@ -1,5 +1,7 @@ */ - public $values; - /** @param non-empty-array $values */ - public function __construct(array $values, bool $from_docblock = false) + public function __construct(public array $values, bool $from_docblock = false) { - $this->values = $values; parent::__construct($from_docblock); } @@ -51,7 +49,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { return 'int'; diff --git a/src/Psalm/Type/Atomic/TIntMaskOf.php b/src/Psalm/Type/Atomic/TIntMaskOf.php index d5a4ae79b62..398491c922b 100644 --- a/src/Psalm/Type/Atomic/TIntMaskOf.php +++ b/src/Psalm/Type/Atomic/TIntMaskOf.php @@ -1,8 +1,8 @@ value = $value; parent::__construct($from_docblock); } @@ -37,7 +30,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { return 'int'; diff --git a/src/Psalm/Type/Atomic/TIntRange.php b/src/Psalm/Type/Atomic/TIntRange.php index dd0c9fa829c..e7006a1c4f0 100644 --- a/src/Psalm/Type/Atomic/TIntRange.php +++ b/src/Psalm/Type/Atomic/TIntRange.php @@ -1,5 +1,7 @@ min_bound = $min_bound; - $this->max_bound = $max_bound; - $this->dependent_list_key = $dependent_list_key; parent::__construct($from_docblock); } @@ -56,7 +43,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return $use_phpdoc_format ? 'int' : diff --git a/src/Psalm/Type/Atomic/TIterable.php b/src/Psalm/Type/Atomic/TIterable.php index 1f67bfb5602..52878f27caa 100644 --- a/src/Psalm/Type/Atomic/TIterable.php +++ b/src/Psalm/Type/Atomic/TIterable.php @@ -1,10 +1,13 @@ @@ -31,15 +35,9 @@ final class TIterable extends Atomic */ public array $type_params; - /** - * @var string - */ - public $value = 'iterable'; + public string $value = 'iterable'; - /** - * @var bool - */ - public $has_docblock_params = false; + public bool $has_docblock_params = false; /** * @param array{Union, Union}|array $type_params @@ -94,7 +92,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return $analysis_php_version_id >= 7_01_00 ? 'iterable' : null; } @@ -160,7 +158,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $types = $this->replaceTypeParamsTemplateTypesWithStandins( $template_result, diff --git a/src/Psalm/Type/Atomic/TKeyOf.php b/src/Psalm/Type/Atomic/TKeyOf.php index 09762e732f9..f8a259a7c76 100644 --- a/src/Psalm/Type/Atomic/TKeyOf.php +++ b/src/Psalm/Type/Atomic/TKeyOf.php @@ -1,8 +1,9 @@ type = $type; parent::__construct($from_docblock); } @@ -36,7 +33,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } @@ -57,7 +54,6 @@ public static function isViableTemplateType(Union $template_type): bool if (!$type instanceof TArray && !$type instanceof TClassConstant && !$type instanceof TKeyedArray - && !$type instanceof TList && !$type instanceof TPropertiesOf ) { return false; @@ -68,15 +64,11 @@ public static function isViableTemplateType(Union $template_type): bool public static function getArrayKeyType( Union $type, - bool $keep_template_params = false + bool $keep_template_params = false, ): ?Union { $key_types = []; foreach ($type->getAtomicTypes() as $atomic_type) { - if ($atomic_type instanceof TList) { - $atomic_type = $atomic_type->getKeyedArray(); - } - if ($atomic_type instanceof TArray) { $array_key_atomics = $atomic_type->type_params[0]; } elseif ($atomic_type instanceof TKeyedArray) { diff --git a/src/Psalm/Type/Atomic/TKeyedArray.php b/src/Psalm/Type/Atomic/TKeyedArray.php index 848ba5f6d5c..a8c16fd28d7 100644 --- a/src/Psalm/Type/Atomic/TKeyedArray.php +++ b/src/Psalm/Type/Atomic/TKeyedArray.php @@ -1,5 +1,7 @@ - */ - public $properties; - - /** - * @var array|null - */ - public $class_strings; - + use UnserializeMemoryUsageSuppressionTrait; /** * If the shape has fallback params then they are here * * @var array{Union, Union}|null */ - public $fallback_params; + public ?array $fallback_params = null; /** * @var bool - if this is a list of sequential elements */ - public $is_list = false; + public bool $is_list = false; /** @var non-empty-lowercase-string */ protected const NAME_ARRAY = 'array'; @@ -67,17 +59,15 @@ class TKeyedArray extends Atomic * @param array $class_strings */ public function __construct( - array $properties, - ?array $class_strings = null, + public array $properties, + public ?array $class_strings = null, ?array $fallback_params = null, bool $is_list = false, - bool $from_docblock = false + bool $from_docblock = false, ) { if ($is_list && $fallback_params) { $fallback_params[0] = Type::getListKey(); } - $this->properties = $properties; - $this->class_strings = $class_strings; $this->fallback_params = $fallback_params; $this->is_list = $is_list; if ($this->is_list) { @@ -221,7 +211,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { return $this->getGenericArrayType()->toNamespacedString( @@ -293,7 +283,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return 'array'; } @@ -362,7 +352,7 @@ public function getGenericValueType(bool $possibly_undefined = false): Union /** * @return TArray|TNonEmptyArray */ - public function getGenericArrayType(bool $allow_non_empty = true, ?string $list_var_id = null): TArray + public function getGenericArrayType(?string $list_var_id = null): TArray { $key_types = []; $value_type = null; @@ -401,7 +391,7 @@ public function getGenericArrayType(bool $allow_non_empty = true, ?string $list_ $key_type = new Union([new TIntRange(0, null, false, $list_var_id)]); } - if ($has_defined_keys && $allow_non_empty) { + if ($has_defined_keys) { return new TNonEmptyArray([$key_type, $value_type]); } return new TArray([$key_type, $value_type]); @@ -417,7 +407,7 @@ public function getGenericArrayType(bool $allow_non_empty = true, ?string $list_ $value_type = $value_type->setPossiblyUndefined(false); - if ($allow_non_empty && ($has_defined_keys || $this->fallback_params !== null)) { + if ($has_defined_keys) { return new TNonEmptyArray([$key_type, $value_type]); } return new TArray([$key_type, $value_type]); @@ -511,7 +501,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { if ($input_type instanceof TKeyedArray && $input_type->is_list @@ -612,7 +602,7 @@ public function replaceTemplateTypesWithStandins( */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $properties = $this->properties; foreach ($properties as $offset => $property) { @@ -647,7 +637,7 @@ protected function getChildNodeKeys(): array public function equals(Atomic $other_type, bool $ensure_source_equality): bool { - if (get_class($other_type) !== static::class) { + if ($other_type::class !== static::class) { return false; } @@ -687,25 +677,7 @@ public function getAssertionString(): string return $this->is_list ? 'list' : 'array'; } - /** - * @deprecated Will be removed in Psalm v6 along with the TList type. - */ - public function getList(): TList - { - if (!$this->is_list) { - throw new UnexpectedValueException('Object-like array must be a list for conversion'); - } - - return $this->isNonEmpty() - ? new TNonEmptyList($this->getGenericValueType()) - : new TList($this->getGenericValueType()); - } - - /** - * @param string|int $name - * @return string|int - */ - private function escapeAndQuote($name) + private function escapeAndQuote(string|int $name): string|int { if (is_string($name)) { $quote = false; diff --git a/src/Psalm/Type/Atomic/TList.php b/src/Psalm/Type/Atomic/TList.php deleted file mode 100644 index 7d102709919..00000000000 --- a/src/Psalm/Type/Atomic/TList.php +++ /dev/null @@ -1,228 +0,0 @@ -type_param = $type_param; - parent::__construct($from_docblock); - } - - /** - * @return static - */ - public function setTypeParam(Union $type_param): self - { - if ($type_param === $this->type_param) { - return $this; - } - $cloned = clone $this; - $cloned->type_param = $type_param; - return $cloned; - } - - public function getKeyedArray(): TKeyedArray - { - return Type::getListAtomic($this->type_param); - } - - public function getId(bool $exact = true, bool $nested = false): string - { - return static::KEY . '<' . $this->type_param->getId($exact) . '>'; - } - - /** - * @param array $aliased_classes - */ - public function toNamespacedString( - ?string $namespace, - array $aliased_classes, - ?string $this_class, - bool $use_phpdoc_format - ): string { - if ($use_phpdoc_format) { - return (new TArray([Type::getInt(), $this->type_param])) - ->toNamespacedString( - $namespace, - $aliased_classes, - $this_class, - true, - ); - } - - return static::KEY - . '<' - . $this->type_param->toNamespacedString( - $namespace, - $aliased_classes, - $this_class, - false, - ) - . '>'; - } - - /** - * @param array $aliased_classes - */ - public function toPhpString( - ?string $namespace, - array $aliased_classes, - ?string $this_class, - int $analysis_php_version_id - ): string { - return 'array'; - } - - public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool - { - return false; - } - - public function getKey(bool $include_extra = true): string - { - return 'array'; - } - - /** - * @psalm-suppress InaccessibleProperty We're only acting on cloned instances - * @return static - */ - public function replaceTemplateTypesWithStandins( - TemplateResult $template_result, - Codebase $codebase, - ?StatementsAnalyzer $statements_analyzer = null, - ?Atomic $input_type = null, - ?int $input_arg_offset = null, - ?string $calling_class = null, - ?string $calling_function = null, - bool $replace = true, - bool $add_lower_bound = false, - int $depth = 0 - ): self { - $cloned = null; - - foreach ([Type::getInt(), $this->type_param] as $offset => $type_param) { - $input_type_param = null; - - if (($input_type instanceof TGenericObject - || $input_type instanceof TIterable - || $input_type instanceof TArray) - && - isset($input_type->type_params[$offset]) - ) { - $input_type_param = $input_type->type_params[$offset]; - } elseif ($input_type instanceof TKeyedArray) { - if ($offset === 0) { - $input_type_param = $input_type->getGenericKeyType(); - } else { - $input_type_param = $input_type->getGenericValueType(); - } - } elseif ($input_type instanceof TList) { - if ($offset === 0) { - continue; - } - - $input_type_param = $input_type->type_param; - } - - $type_param = TemplateStandinTypeReplacer::replace( - $type_param, - $template_result, - $codebase, - $statements_analyzer, - $input_type_param, - $input_arg_offset, - $calling_class, - $calling_function, - $replace, - $add_lower_bound, - null, - $depth + 1, - ); - - if ($offset === 1 && ($cloned || $this->type_param !== $type_param)) { - $cloned ??= clone $this; - $cloned->type_param = $type_param; - } - } - - return $cloned ?? $this; - } - - /** - * @return static - */ - public function replaceTemplateTypesWithArgTypes( - TemplateResult $template_result, - ?Codebase $codebase - ): self { - return $this->setTypeParam(TemplateInferredTypeReplacer::replace( - $this->type_param, - $template_result, - $codebase, - )); - } - - public function equals(Atomic $other_type, bool $ensure_source_equality): bool - { - if (get_class($other_type) !== static::class) { - return false; - } - - if (!$this->type_param->equals($other_type->type_param, $ensure_source_equality, false)) { - return false; - } - - return true; - } - - public function getAssertionString(): string - { - if ($this->type_param->isMixed()) { - return 'list'; - } - - return $this->getId(); - } - - protected function getChildNodeKeys(): array - { - return ['type_param']; - } -} diff --git a/src/Psalm/Type/Atomic/TLiteralClassString.php b/src/Psalm/Type/Atomic/TLiteralClassString.php index c5483511c9e..85bd43f2189 100644 --- a/src/Psalm/Type/Atomic/TLiteralClassString.php +++ b/src/Psalm/Type/Atomic/TLiteralClassString.php @@ -1,11 +1,13 @@ definite_class = $definite_class; } public function getKey(bool $include_extra = true): string @@ -40,7 +39,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return 'string'; } @@ -71,7 +70,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { return 'string'; @@ -93,7 +92,7 @@ public function toNamespacedString( ) . '::class'; } - if (!$namespace && strpos($this->value, '\\') === false) { + if (!$namespace && !str_contains($this->value, '\\')) { return $this->value . '::class'; } diff --git a/src/Psalm/Type/Atomic/TLiteralFloat.php b/src/Psalm/Type/Atomic/TLiteralFloat.php index c4e1a7fe456..1565183108b 100644 --- a/src/Psalm/Type/Atomic/TLiteralFloat.php +++ b/src/Psalm/Type/Atomic/TLiteralFloat.php @@ -1,5 +1,7 @@ value = $value; parent::__construct($from_docblock); } @@ -39,7 +37,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return 'float'; } diff --git a/src/Psalm/Type/Atomic/TLiteralInt.php b/src/Psalm/Type/Atomic/TLiteralInt.php index 8055974d6f7..ae28af3df30 100644 --- a/src/Psalm/Type/Atomic/TLiteralInt.php +++ b/src/Psalm/Type/Atomic/TLiteralInt.php @@ -1,5 +1,7 @@ value = $value; parent::__construct($from_docblock); } @@ -44,7 +42,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return $use_phpdoc_format ? 'int' : (string) $this->value; } diff --git a/src/Psalm/Type/Atomic/TLiteralString.php b/src/Psalm/Type/Atomic/TLiteralString.php index 6a622887d55..f235fdfb95b 100644 --- a/src/Psalm/Type/Atomic/TLiteralString.php +++ b/src/Psalm/Type/Atomic/TLiteralString.php @@ -1,5 +1,7 @@ value . "'"; } diff --git a/src/Psalm/Type/Atomic/TLowercaseString.php b/src/Psalm/Type/Atomic/TLowercaseString.php index b65ac5e9dcb..f9eac221716 100644 --- a/src/Psalm/Type/Atomic/TLowercaseString.php +++ b/src/Psalm/Type/Atomic/TLowercaseString.php @@ -1,5 +1,7 @@ from_loop_isset = $from_loop_isset; parent::__construct($from_docblock); } @@ -32,7 +32,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return $analysis_php_version_id >= 8_00_00 ? 'mixed' : null; } diff --git a/src/Psalm/Type/Atomic/TNamedObject.php b/src/Psalm/Type/Atomic/TNamedObject.php index ee7c5d38ea5..7d7e9a68c0f 100644 --- a/src/Psalm/Type/Atomic/TNamedObject.php +++ b/src/Psalm/Type/Atomic/TNamedObject.php @@ -1,5 +1,7 @@ value = $value; - $this->is_static = $is_static; - $this->definite_class = $definite_class; $this->extra_types = $extra_types; parent::__construct($from_docblock); } @@ -148,7 +133,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($this->value === 'static') { return 'static'; @@ -178,7 +163,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { if ($this->value === 'static') { return $analysis_php_version_id >= 8_00_00 ? 'static' : null; @@ -206,7 +191,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $intersection = $this->replaceIntersectionTemplateTypesWithArgTypes($template_result, $codebase); if (!$intersection) { @@ -230,7 +215,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $intersection = $this->replaceIntersectionTemplateTypesWithStandins( $template_result, @@ -264,7 +249,7 @@ public static function createFromName( bool $is_static = false, bool $definite_class = false, array $extra_types = [], - bool $from_docblock = false + bool $from_docblock = false, ): TNamedObject { if ($value === 'Closure') { return new TClosure($value, null, null, null, [], $extra_types, $from_docblock); diff --git a/src/Psalm/Type/Atomic/TNever.php b/src/Psalm/Type/Atomic/TNever.php index 397424b860a..6121a752b05 100644 --- a/src/Psalm/Type/Atomic/TNever.php +++ b/src/Psalm/Type/Atomic/TNever.php @@ -1,7 +1,10 @@ count = $count; - $this->min_count = $min_count; - $this->value = $value; parent::__construct($type_params, $from_docblock); } diff --git a/src/Psalm/Type/Atomic/TNonEmptyList.php b/src/Psalm/Type/Atomic/TNonEmptyList.php deleted file mode 100644 index 47c628ccd7e..00000000000 --- a/src/Psalm/Type/Atomic/TNonEmptyList.php +++ /dev/null @@ -1,94 +0,0 @@ -count = $count; - $this->min_count = $min_count; - /** @psalm-suppress DeprecatedClass */ - parent::__construct($type_param, $from_docblock); - } - - public function getKeyedArray(): TKeyedArray - { - if (!$this->count && !$this->min_count) { - return Type::getNonEmptyListAtomic($this->type_param); - } - if ($this->count) { - return new TKeyedArray( - array_fill(0, $this->count, $this->type_param), - null, - null, - true, - $this->from_docblock, - ); - } - return new TKeyedArray( - array_fill(0, $this->min_count, $this->type_param), - null, - [Type::getListKey(), $this->type_param], - true, - $this->from_docblock, - ); - } - - - /** - * @param positive-int|null $count - * @return static - */ - public function setCount(?int $count): self - { - if ($count === $this->count) { - return $this; - } - $cloned = clone $this; - $cloned->count = $count; - return $cloned; - } - - public function getAssertionString(): string - { - return 'non-empty-list'; - } -} diff --git a/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php b/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php index a72f2a6e811..e9872592e9b 100644 --- a/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php +++ b/src/Psalm/Type/Atomic/TNonEmptyLowercaseString.php @@ -1,5 +1,7 @@ = 7_02_00 ? $this->getKey() : null; } diff --git a/src/Psalm/Type/Atomic/TObjectWithProperties.php b/src/Psalm/Type/Atomic/TObjectWithProperties.php index b681459bfaa..93c60e99dd1 100644 --- a/src/Psalm/Type/Atomic/TObjectWithProperties.php +++ b/src/Psalm/Type/Atomic/TObjectWithProperties.php @@ -1,5 +1,7 @@ - */ - public $properties; - - /** - * @var array - */ - public $methods; - - /** @var bool */ - public $is_stringable_object_only = false; + public bool $is_stringable_object_only = false; /** * Constructs a new instance of a generic type @@ -45,13 +36,11 @@ final class TObjectWithProperties extends TObject * @param array $extra_types */ public function __construct( - array $properties, - array $methods = [], + public array $properties, + public array $methods = [], array $extra_types = [], - bool $from_docblock = false + bool $from_docblock = false, ) { - $this->properties = $properties; - $this->methods = $methods; $this->extra_types = $extra_types; $this->is_stringable_object_only = @@ -140,7 +129,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { return 'object'; @@ -178,7 +167,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return $this->getKey(); } @@ -228,7 +217,7 @@ public function replaceTemplateTypesWithStandins( ?string $calling_function = null, bool $replace = true, bool $add_lower_bound = false, - int $depth = 0 + int $depth = 0, ): self { $properties = []; @@ -280,7 +269,7 @@ public function replaceTemplateTypesWithStandins( */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $properties = $this->properties; foreach ($properties as $offset => $property) { diff --git a/src/Psalm/Type/Atomic/TPropertiesOf.php b/src/Psalm/Type/Atomic/TPropertiesOf.php index 49da46df53d..c9df9d0bd32 100644 --- a/src/Psalm/Type/Atomic/TPropertiesOf.php +++ b/src/Psalm/Type/Atomic/TPropertiesOf.php @@ -1,7 +1,10 @@ */ @@ -43,12 +41,10 @@ public static function tokenNames(): array * @param self::VISIBILITY_*|null $visibility_filter */ public function __construct( - TNamedObject $classlike_type, - ?int $visibility_filter, - bool $from_docblock = false + public TNamedObject $classlike_type, + public ?int $visibility_filter, + bool $from_docblock = false, ) { - $this->classlike_type = $classlike_type; - $this->visibility_filter = $visibility_filter; parent::__construct($from_docblock); } @@ -57,16 +53,12 @@ public function __construct( */ public static function filterForTokenName(string $token_name): ?int { - switch ($token_name) { - case 'public-properties-of': - return self::VISIBILITY_PUBLIC; - case 'protected-properties-of': - return self::VISIBILITY_PROTECTED; - case 'private-properties-of': - return self::VISIBILITY_PRIVATE; - default: - return null; - } + return match ($token_name) { + 'public-properties-of' => self::VISIBILITY_PUBLIC, + 'protected-properties-of' => self::VISIBILITY_PROTECTED, + 'private-properties-of' => self::VISIBILITY_PRIVATE, + default => null, + }; } /** @@ -75,16 +67,12 @@ public static function filterForTokenName(string $token_name): ?int */ public static function tokenNameForFilter(?int $visibility_filter): string { - switch ($visibility_filter) { - case self::VISIBILITY_PUBLIC: - return 'public-properties-of'; - case self::VISIBILITY_PROTECTED: - return 'protected-properties-of'; - case self::VISIBILITY_PRIVATE: - return 'private-properties-of'; - default: - return 'properties-of'; - } + return match ($visibility_filter) { + self::VISIBILITY_PUBLIC => 'public-properties-of', + self::VISIBILITY_PROTECTED => 'protected-properties-of', + self::VISIBILITY_PRIVATE => 'private-properties-of', + default => 'properties-of', + }; } protected function getChildNodeKeys(): array @@ -104,7 +92,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return $this->getKey(); } diff --git a/src/Psalm/Type/Atomic/TResource.php b/src/Psalm/Type/Atomic/TResource.php index 1d99eef800c..f67527df27a 100644 --- a/src/Psalm/Type/Atomic/TResource.php +++ b/src/Psalm/Type/Atomic/TResource.php @@ -1,7 +1,10 @@ = 7_00_00 ? 'string' : null; } diff --git a/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php b/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php index 80c7db23408..102b8c0e5af 100644 --- a/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php +++ b/src/Psalm/Type/Atomic/TTemplateIndexedAccess.php @@ -1,7 +1,10 @@ array_param_name = $array_param_name; - $this->offset_param_name = $offset_param_name; - $this->defining_class = $defining_class; parent::__construct($from_docblock); } @@ -48,7 +34,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } diff --git a/src/Psalm/Type/Atomic/TTemplateKeyOf.php b/src/Psalm/Type/Atomic/TTemplateKeyOf.php index debb1fc8fa1..c6a728c88a2 100644 --- a/src/Psalm/Type/Atomic/TTemplateKeyOf.php +++ b/src/Psalm/Type/Atomic/TTemplateKeyOf.php @@ -1,10 +1,13 @@ param_name = $param_name; - $this->defining_class = $defining_class; - $this->as = $as; parent::__construct($from_docblock); } @@ -63,7 +49,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return 'key-of<' . $this->param_name . '>'; } @@ -75,7 +61,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } @@ -90,7 +76,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $as = TemplateInferredTypeReplacer::replace( $this->as, diff --git a/src/Psalm/Type/Atomic/TTemplateParam.php b/src/Psalm/Type/Atomic/TTemplateParam.php index f1bb94b88be..84169899a52 100644 --- a/src/Psalm/Type/Atomic/TTemplateParam.php +++ b/src/Psalm/Type/Atomic/TTemplateParam.php @@ -1,9 +1,12 @@ $extra_types */ public function __construct( - string $param_name, - Union $extends, - string $defining_class, + public string $param_name, + public Union $as, + public string $defining_class, array $extra_types = [], - bool $from_docblock = false + bool $from_docblock = false, ) { - $this->param_name = $param_name; - $this->as = $extends; - $this->defining_class = $defining_class; $this->extra_types = $extra_types; parent::__construct($from_docblock); } @@ -103,7 +89,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } @@ -115,7 +101,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { if ($use_phpdoc_format) { return $this->as->toNamespacedString( @@ -151,7 +137,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $intersection = $this->replaceIntersectionTemplateTypesWithArgTypes($template_result, $codebase); if (!$intersection) { diff --git a/src/Psalm/Type/Atomic/TTemplateParamClass.php b/src/Psalm/Type/Atomic/TTemplateParamClass.php index ccf40a27c8c..8bbbf7f3769 100644 --- a/src/Psalm/Type/Atomic/TTemplateParamClass.php +++ b/src/Psalm/Type/Atomic/TTemplateParamClass.php @@ -1,5 +1,7 @@ param_name = $param_name; - $this->defining_class = $defining_class; parent::__construct( $as, $as_type, @@ -61,7 +51,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return $this->param_name . '::class'; } diff --git a/src/Psalm/Type/Atomic/TTemplatePropertiesOf.php b/src/Psalm/Type/Atomic/TTemplatePropertiesOf.php index 9dbc710c147..48cd32f857d 100644 --- a/src/Psalm/Type/Atomic/TTemplatePropertiesOf.php +++ b/src/Psalm/Type/Atomic/TTemplatePropertiesOf.php @@ -1,10 +1,13 @@ param_name = $param_name; - $this->defining_class = $defining_class; - $this->as = $as; - $this->visibility_filter = $visibility_filter; parent::__construct($from_docblock); } @@ -70,7 +53,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): string { return $this->getKey(); } @@ -85,7 +68,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $param = new TTemplateParam( $this->as->param_name, diff --git a/src/Psalm/Type/Atomic/TTemplateValueOf.php b/src/Psalm/Type/Atomic/TTemplateValueOf.php index 6d00b32ea8c..23d33af5de0 100644 --- a/src/Psalm/Type/Atomic/TTemplateValueOf.php +++ b/src/Psalm/Type/Atomic/TTemplateValueOf.php @@ -1,10 +1,13 @@ param_name = $param_name; - $this->defining_class = $defining_class; - $this->as = $as; parent::__construct($from_docblock); } @@ -63,7 +49,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { return 'value-of<' . $this->param_name . '>'; } @@ -75,7 +61,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } @@ -90,7 +76,7 @@ public function canBeFullyExpressedInPhp(int $analysis_php_version_id): bool */ public function replaceTemplateTypesWithArgTypes( TemplateResult $template_result, - ?Codebase $codebase + ?Codebase $codebase, ): self { $as = TemplateInferredTypeReplacer::replace( $this->as, diff --git a/src/Psalm/Type/Atomic/TTraitString.php b/src/Psalm/Type/Atomic/TTraitString.php index 07f38880e2a..fbad80e98a0 100644 --- a/src/Psalm/Type/Atomic/TTraitString.php +++ b/src/Psalm/Type/Atomic/TTraitString.php @@ -1,5 +1,7 @@ |null - * @deprecated type aliases are resolved within {@see TypeParser::resolveTypeAliases()} and therefore the - * referencing type(s) are part of other intersection types. The intersection types are not set anymore - * and with v6 this property along with its related methods will get removed. - */ - public $extra_types; - - /** @var string */ - public $declaring_fq_classlike_name; - - /** @var string */ - public $alias_name; - - /** - * @param array|null $extra_types - */ - public function __construct(string $declaring_fq_classlike_name, string $alias_name, ?array $extra_types = null) - { - $this->declaring_fq_classlike_name = $declaring_fq_classlike_name; - $this->alias_name = $alias_name; - /** @psalm-suppress DeprecatedProperty For backwards compatibility, we have to keep this here. */ - $this->extra_types = $extra_types; + use UnserializeMemoryUsageSuppressionTrait; + public function __construct( + public string $declaring_fq_classlike_name, + public string $alias_name, + ) { parent::__construct(true); } - /** - * @param array|null $extra_types - * @deprecated type aliases are resolved within {@see TypeParser::resolveTypeAliases()} and therefore the - * referencing type(s) are part of other intersection types. This method will get removed with v6. - * @psalm-suppress PossiblyUnusedMethod For backwards compatibility, we have to keep this here. - */ - public function setIntersectionTypes(?array $extra_types): self - { - /** @psalm-suppress DeprecatedProperty For backwards compatibility, we have to keep this here. */ - if ($extra_types === $this->extra_types) { - return $this; - } - return new self( - $this->declaring_fq_classlike_name, - $this->alias_name, - $extra_types, - ); - } public function getKey(bool $include_extra = true): string { @@ -63,17 +27,6 @@ public function getKey(bool $include_extra = true): string public function getId(bool $exact = true, bool $nested = false): string { - /** @psalm-suppress DeprecatedProperty For backwards compatibility, we have to keep this here. */ - if ($this->extra_types) { - return $this->getKey() . '&' . implode( - '&', - array_map( - static fn(Atomic $type): string => $type->getId($exact, true), - $this->extra_types, - ), - ); - } - return $this->getKey(); } @@ -84,7 +37,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } diff --git a/src/Psalm/Type/Atomic/TUnknownClassString.php b/src/Psalm/Type/Atomic/TUnknownClassString.php index 765b15e9e93..c46cf16c2b5 100644 --- a/src/Psalm/Type/Atomic/TUnknownClassString.php +++ b/src/Psalm/Type/Atomic/TUnknownClassString.php @@ -1,5 +1,7 @@ as_unknown_type = $as_unknown_type; } } diff --git a/src/Psalm/Type/Atomic/TValueOf.php b/src/Psalm/Type/Atomic/TValueOf.php index eb8df8ce03a..d85d3babaa0 100644 --- a/src/Psalm/Type/Atomic/TValueOf.php +++ b/src/Psalm/Type/Atomic/TValueOf.php @@ -1,12 +1,13 @@ type = $type; parent::__construct($from_docblock); } @@ -35,21 +33,22 @@ public function __construct(Union $type, bool $from_docblock = false) private static function getValueTypeForNamedObject( array $cases, TNamedObject $atomic_type, - Codebase $codebase + Codebase $codebase, ): Union { if ($atomic_type instanceof TEnumCase) { assert(isset($cases[$atomic_type->case_name]), 'Should\'ve been verified in TValueOf#getValueType'); $value = $cases[$atomic_type->case_name]->getValue($codebase->classlikes); assert($value !== null, 'Backed enum must have a value.'); - return new Union([ConstantTypeResolver::getLiteralTypeFromScalarValue($value)]); + + return new Union([$value]); } return new Union(array_map( static function (EnumCaseStorage $case) use ($codebase): Atomic { $case_value = $case->getValue($codebase->classlikes); - assert($case_value !== null); // Backed enum must have a value - return ConstantTypeResolver::getLiteralTypeFromScalarValue($case_value); + assert($case_value !== null); + return $case_value; }, array_values($cases), )); @@ -73,7 +72,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { return null; } @@ -94,7 +93,6 @@ public static function isViableTemplateType(Union $template_type): bool if (!$type instanceof TArray && !$type instanceof TClassConstant && !$type instanceof TKeyedArray - && !$type instanceof TList && !$type instanceof TPropertiesOf && !$type instanceof TNamedObject ) { @@ -107,15 +105,13 @@ public static function isViableTemplateType(Union $template_type): bool public static function getValueType( Union $type, Codebase $codebase, - bool $keep_template_params = false + bool $keep_template_params = false, ): ?Union { $value_types = []; foreach ($type->getAtomicTypes() as $atomic_type) { if ($atomic_type instanceof TArray) { $value_atomics = $atomic_type->type_params[1]; - } elseif ($atomic_type instanceof TList) { - $value_atomics = $atomic_type->type_param; } elseif ($atomic_type instanceof TKeyedArray) { $value_atomics = $atomic_type->getGenericValueType(); } elseif ($atomic_type instanceof TTemplateParam) { diff --git a/src/Psalm/Type/Atomic/TVoid.php b/src/Psalm/Type/Atomic/TVoid.php index e122d9a8e1c..d1cd2faf60c 100644 --- a/src/Psalm/Type/Atomic/TVoid.php +++ b/src/Psalm/Type/Atomic/TVoid.php @@ -1,7 +1,10 @@ = 7_01_00 ? $this->getKey() : null; } diff --git a/src/Psalm/Type/MutableTypeVisitor.php b/src/Psalm/Type/MutableTypeVisitor.php index a61f69dad9f..c081f35ccce 100644 --- a/src/Psalm/Type/MutableTypeVisitor.php +++ b/src/Psalm/Type/MutableTypeVisitor.php @@ -1,5 +1,7 @@ bar) into a different sort of issue - * - * @var bool */ - public $from_property = false; + public bool $from_property = false; /** * Whether the type originated from *static* property * * Unlike non-static properties, static properties have no prescribed place * like __construct() to be initialized in - * - * @var bool */ - public $from_static_property = false; + public bool $from_static_property = false; /** * Whether the property that this type has been derived from has been initialized in a constructor - * - * @var bool */ - public $initialized = true; + public bool $initialized = true; /** * Which class the type was initialised in - * - * @var ?string */ - public $initialized_class; + public ?string $initialized_class = null; /** * Whether or not the type has been checked yet - * - * @var bool */ - public $checked = false; + public bool $checked = false; - /** - * @var bool - */ - public $failed_reconciliation = false; + public bool $failed_reconciliation = false; /** * Whether or not to ignore issues with possibly-null values - * - * @var bool */ - public $ignore_nullable_issues = false; + public bool $ignore_nullable_issues = false; /** * Whether or not to ignore issues with possibly-false values - * - * @var bool */ - public $ignore_falsable_issues = false; + public bool $ignore_falsable_issues = false; /** * Whether or not to ignore issues with isset on this type - * - * @var bool */ - public $ignore_isset = false; + public bool $ignore_isset = false; /** * Whether or not this variable is possibly undefined - * - * @var bool */ - public $possibly_undefined = false; + public bool $possibly_undefined = false; /** * Whether or not this variable is possibly undefined - * - * @var bool */ - public $possibly_undefined_from_try = false; + public bool $possibly_undefined_from_try = false; /** * whether this type had never set explicitly * since it's the bottom type, it's combined into everything else and lost * * @psalm-suppress PossiblyUnusedProperty used in setTypes and addType - * @var bool */ - public $explicit_never = false; + public bool $explicit_never = false; /** * Whether or not this union had a template, since replaced - * - * @var bool */ - public $had_template = false; + public bool $had_template = false; /** * Whether or not this union comes from a template "as" default - * - * @var bool */ - public $from_template_default = false; + public bool $from_template_default = false; /** * @var array @@ -178,25 +147,14 @@ final class MutableUnion implements TypeNode * True if the type was passed or returned by reference, or if the type refers to an object's * property or an item in an array. Note that this is not true for locally created references * that don't refer to properties or array items (see Context::$references_in_scope). - * - * @var bool */ - public $by_ref = false; + public bool $by_ref = false; - /** - * @var bool - */ - public $reference_free = false; + public bool $reference_free = false; - /** - * @var bool - */ - public $allow_mutations = true; + public bool $allow_mutations = true; - /** - * @var bool - */ - public $has_mutations = true; + public bool $has_mutations = true; /** * This is a cache of getId on non-exact mode @@ -212,12 +170,9 @@ final class MutableUnion implements TypeNode /** * @var array */ - public $parent_nodes = []; + public array $parent_nodes = []; - /** - * @var bool - */ - public $different = false; + public bool $different = false; /** @psalm-suppress PossiblyUnusedProperty */ public bool $propagate_parent_nodes = false; @@ -381,10 +336,8 @@ public function bustCache(): void /** * @psalm-external-mutation-free - * @param Union|MutableUnion $old_type - * @param Union|MutableUnion|null $new_type */ - public function substitute($old_type, $new_type = null): self + public function substitute(Union|MutableUnion $old_type, Union|MutableUnion|null $new_type = null): self { if ($this->hasMixed() && !$this->isEmptyMixed()) { return $this; @@ -455,7 +408,7 @@ public function substitute($old_type, $new_type = null): self foreach ($new_type->types as $key => $new_type_part) { if (!isset($this->types[$key]) || ($new_type_part instanceof Scalar - && get_class($new_type_part) === get_class($this->types[$key])) + && $new_type_part::class === $this->types[$key]::class) ) { $this->types[$key] = $new_type_part; } else { diff --git a/src/Psalm/Type/Reconciler.php b/src/Psalm/Type/Reconciler.php index b520ab9d52f..69dcac35a43 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -1,5 +1,7 @@ $existing_referenced) { if ($existing_referenced === $new_key) { @@ -468,7 +471,7 @@ private static function addNestedAssertions(array $new_types, array $existing_ty $divider = array_shift($key_parts); if ($divider === '[') { - $array_key = array_shift($key_parts); + $array_key = (string) array_shift($key_parts); array_shift($key_parts); if ($array_key[0] === '\'' || $array_key[0] === '"') { @@ -479,7 +482,7 @@ private static function addNestedAssertions(array $new_types, array $existing_ty $new_base_key = $base_key . '[' . $array_key . ']'; - if (is_string($array_key) && strpos($array_key, '\'') !== false) { + if (is_string($array_key) && str_contains($array_key, '\'')) { $new_types[$base_key][] = [new HasStringArrayAccess()]; } else { $new_types[$base_key][] = [new HasIntOrStringArrayAccess()]; @@ -667,7 +670,7 @@ private static function getValueForKey( bool $has_inverted_key_exists, bool $has_empty, bool $inside_loop, - bool &$has_object_array_access + bool &$has_object_array_access, ): ?Union { $key_parts = self::breakUpPathIntoParts($key); @@ -711,7 +714,7 @@ private static function getValueForKey( $divider = array_shift($key_parts); if ($divider === '[') { - $array_key = array_shift($key_parts); + $array_key = (string) array_shift($key_parts); array_shift($key_parts); $new_base_key = $base_key . '[' . $array_key . ']'; @@ -724,9 +727,7 @@ private static function getValueForKey( while ($atomic_types) { $existing_key_type_part = array_shift($atomic_types); - if ($existing_key_type_part instanceof TList) { - $existing_key_type_part = $existing_key_type_part->getKeyedArray(); - } + if ($existing_key_type_part instanceof TTemplateParam) { $atomic_types = array_merge($atomic_types, $existing_key_type_part->as->getAtomicTypes()); @@ -827,7 +828,7 @@ private static function getValueForKey( $base_key = $new_base_key; } elseif ($divider === '->' || $divider === '::$') { - $property_name = array_shift($key_parts); + $property_name = (string) array_shift($key_parts); $new_base_key = $base_key . $divider . $property_name; if (!isset($existing_keys[$new_base_key])) { @@ -855,7 +856,7 @@ private static function getValueForKey( if (!$codebase->classOrInterfaceExists($existing_key_type_part->value)) { $class_property_type = Type::getMixed(); } else { - if (substr($property_name, -2) === '()') { + if (str_ends_with($property_name, '()')) { $method_id = new MethodIdentifier( $existing_key_type_part->value, strtolower(substr($property_name, 0, -2)), @@ -944,7 +945,7 @@ private static function getValueForKey( private static function getPropertyType( Codebase $codebase, string $fq_class_name, - string $property_name + string $property_name, ): ?Union { $property_id = $fq_class_name . '::$' . $property_name; @@ -953,11 +954,7 @@ private static function getPropertyType( $fq_class_name, ); - if (isset($declaring_class_storage->pseudo_property_get_types['$' . $property_name])) { - return $declaring_class_storage->pseudo_property_get_types['$' . $property_name]; - } - - return null; + return $declaring_class_storage->pseudo_property_get_types['$' . $property_name] ?? null; } $declaring_property_class = $codebase->properties->getDeclaringClassForProperty( @@ -994,18 +991,17 @@ private static function getPropertyType( } /** - * @param Union|MutableUnion $existing_var_type * @param string[] $suppressed_issues */ protected static function triggerIssueForImpossible( - $existing_var_type, + Union|MutableUnion $existing_var_type, string $old_var_type_string, string $key, Assertion $assertion, bool $redundant, bool $negated, CodeLocation $code_location, - array $suppressed_issues + array $suppressed_issues, ): void { $assertion_string = (string)$assertion; $not = $assertion_string[0] === '!'; @@ -1127,7 +1123,7 @@ private static function adjustTKeyedArrayType( array $key_parts, array &$existing_types, array &$changed_var_ids, - Union $result_type + Union $result_type, ): void { array_pop($key_parts); $array_key = array_pop($key_parts); @@ -1157,18 +1153,15 @@ private static function adjustTKeyedArrayType( ; } - $base_key = implode($key_parts); + $base_key = implode('', $key_parts); $result_type = $result_type->setPossiblyUndefined( $result_type->possibly_undefined || count($array_key_offsets) > 1, ); foreach ($array_key_offsets as $array_key_offset) { - if (isset($existing_types[$base_key]) && $array_key_offset !== false) { + if (isset($existing_types[$base_key])) { foreach ($existing_types[$base_key]->getAtomicTypes() as $base_atomic_type) { - if ($base_atomic_type instanceof TList) { - $base_atomic_type = $base_atomic_type->getKeyedArray(); - } if ($base_atomic_type instanceof TKeyedArray || ($base_atomic_type instanceof TArray && !$base_atomic_type->isEmptyArray()) diff --git a/src/Psalm/Type/TaintKind.php b/src/Psalm/Type/TaintKind.php index ea76b6c90a5..5b2ac02a8ae 100644 --- a/src/Psalm/Type/TaintKind.php +++ b/src/Psalm/Type/TaintKind.php @@ -1,5 +1,7 @@ bar) into a different sort of issue - * - * @var bool */ - public $from_property = false; + public bool $from_property = false; /** * Whether the type originated from *static* property * * Unlike non-static properties, static properties have no prescribed place * like __construct() to be initialized in - * - * @var bool */ - public $from_static_property = false; + public bool $from_static_property = false; /** * Whether the property that this type has been derived from has been initialized in a constructor - * - * @var bool */ - public $initialized = true; + public bool $initialized = true; /** * Which class the type was initialised in - * - * @var ?string */ - public $initialized_class; + public ?string $initialized_class = null; /** * Whether or not the type has been checked yet - * - * @var bool */ - public $checked = false; + public bool $checked = false; - /** - * @var bool - */ - public $failed_reconciliation = false; + public bool $failed_reconciliation = false; /** * Whether or not to ignore issues with possibly-null values - * - * @var bool */ - public $ignore_nullable_issues = false; + public bool $ignore_nullable_issues = false; /** * Whether or not to ignore issues with possibly-false values - * - * @var bool */ - public $ignore_falsable_issues = false; + public bool $ignore_falsable_issues = false; /** * Whether or not to ignore issues with isset on this type - * - * @var bool */ - public $ignore_isset = false; + public bool $ignore_isset = false; /** * Whether or not this variable is possibly undefined - * - * @var bool */ - public $possibly_undefined = false; + public bool $possibly_undefined = false; /** * Whether or not this variable is possibly undefined - * - * @var bool */ - public $possibly_undefined_from_try = false; + public bool $possibly_undefined_from_try = false; /** * whether this type had never set explicitly * since it's the bottom type, it's combined into everything else and lost - * - * @var bool */ - public $explicit_never = false; + public bool $explicit_never = false; /** * Whether or not this union had a template, since replaced - * - * @var bool */ - public $had_template = false; + public bool $had_template = false; /** * Whether or not this union comes from a template "as" default - * - * @var bool */ - public $from_template_default = false; + public bool $from_template_default = false; /** * @var array @@ -190,25 +159,14 @@ final class Union implements TypeNode * True if the type was passed or returned by reference, or if the type refers to an object's * property or an item in an array. Note that this is not true for locally created references * that don't refer to properties or array items (see Context::$references_in_scope). - * - * @var bool */ - public $by_ref = false; + public bool $by_ref = false; - /** - * @var bool - */ - public $reference_free = false; + public bool $reference_free = false; - /** - * @var bool - */ - public $allow_mutations = true; + public bool $allow_mutations = true; - /** - * @var bool - */ - public $has_mutations = true; + public bool $has_mutations = true; /** * This is a cache of getId on non-exact mode @@ -218,20 +176,17 @@ final class Union implements TypeNode /** * This is a cache of getId on exact mode */ - private ?string $exact_id; + private ?string $exact_id = null; /** * @var array */ - public $parent_nodes = []; + public array $parent_nodes = []; public bool $propagate_parent_nodes = false; - /** - * @var bool - */ - public $different = false; + public bool $different = false; private const PROPERTY_KEYS_FOR_UNSERIALIZE = [ "\0" . self::class . "\0" . 'types' => 'types', @@ -361,7 +316,7 @@ public function setPossiblyUndefined(bool $possibly_undefined, ?bool $from_try = } /** @return static */ - public function setByRef(bool $by_ref) + public function setByRef(bool $by_ref): static { if ($by_ref === $this->by_ref) { return $this; diff --git a/src/Psalm/Type/UnionTrait.php b/src/Psalm/Type/UnionTrait.php index b471b795df3..40a9cf6633b 100644 --- a/src/Psalm/Type/UnionTrait.php +++ b/src/Psalm/Type/UnionTrait.php @@ -1,5 +1,7 @@ $types @@ -218,7 +219,7 @@ public function getId(bool $exact = true): string if (count($types) > 1) { foreach ($types as $i => $type) { - if (strpos($type, ' as ') && strpos($type, '(') === false) { + if (strpos($type, ' as ') && !str_contains($type, '(')) { $types[$i] = '(' . $type . ')'; } } @@ -245,7 +246,7 @@ public function toNamespacedString( ?string $namespace, array $aliased_classes, ?string $this_class, - bool $use_phpdoc_format + bool $use_phpdoc_format, ): string { $other_types = []; @@ -262,9 +263,9 @@ public function toNamespacedString( } elseif ($type instanceof TLiteralString) { $literal_strings[] = $type_string; } else { - if (get_class($type) === TString::class) { + if ($type::class === TString::class) { $has_non_literal_string = true; - } elseif (get_class($type) === TInt::class) { + } elseif ($type::class === TInt::class) { $has_non_literal_int = true; } $other_types[] = $type_string; @@ -295,7 +296,7 @@ public function toPhpString( ?string $namespace, array $aliased_classes, ?string $this_class, - int $analysis_php_version_id + int $analysis_php_version_id, ): ?string { if (!$this->isSingleAndMaybeNullable()) { if ($analysis_php_version_id < 8_00_00) { @@ -406,9 +407,6 @@ public function hasArray(): bool */ public function getArray(): Atomic { - if ($this->types['array'] instanceof TList) { - return $this->types['array']->getKeyedArray(); - } return $this->types['array']; } @@ -829,7 +827,7 @@ public function isEmptyMixed(): bool public function isVanillaMixed(): bool { return isset($this->types['mixed']) - && get_class($this->types['mixed']) === TMixed::class + && $this->types['mixed']::class === TMixed::class && !$this->types['mixed']->from_loop_isset && count($this->types) === 1; } @@ -1213,9 +1211,9 @@ public function isSingleLiteral(): bool /** * @psalm-mutation-free - * @return TLiteralInt|TLiteralString|TLiteralFloat + * @psalm-suppress InvalidFalsableReturnType */ - public function getSingleLiteral() + public function getSingleLiteral(): TLiteralInt|TLiteralString|TLiteralFloat { if (!$this->isSingleLiteral()) { throw new InvalidArgumentException("Not a single literal"); @@ -1247,7 +1245,7 @@ public function hasLiteralInt(): bool /** * @psalm-mutation-free - * @return bool true if this is a int literal with only one possible value + * @return bool true if this is an int literal with only one possible value */ public function isSingleIntLiteral(): bool { @@ -1280,7 +1278,7 @@ public function check( bool $inferred = true, bool $inherited = false, bool $prevent_template_covariance = false, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): bool { if ($this->checked) { return true; @@ -1311,7 +1309,7 @@ public function check( public function queueClassLikesForScanning( Codebase $codebase, ?FileStorage $file_storage = null, - array $phantom_classes = [] + array $phantom_classes = [], ): void { $scanner_visitor = new TypeScanner( $codebase->scanner, @@ -1382,7 +1380,7 @@ public function equals( self $other_type, bool $ensure_source_equality = true, bool $ensure_parent_node_equality = true, - bool $ensure_possibly_undefined_equality = true + bool $ensure_possibly_undefined_equality = true, ): bool { if ($other_type === $this) { return true; diff --git a/stubs/CoreGenericClasses.phpstub b/stubs/CoreGenericClasses.phpstub index 93cb8cb42f0..85a03d0a0b8 100644 --- a/stubs/CoreGenericClasses.phpstub +++ b/stubs/CoreGenericClasses.phpstub @@ -74,7 +74,7 @@ class Generator implements Traversable { interface ArrayAccess { /** - * Whether a offset exists + * Whether an offset exists * @link http://php.net/manual/en/arrayaccess.offsetexists.php * * @param TKey $offset An offset to check for. @@ -501,9 +501,7 @@ final class WeakMap implements ArrayAccess, Countable, IteratorAggregate, Traver */ public function offsetUnset($offset) {} - /** - * @return Traversable - */ + /** @return Traversable */ public function getIterator() { } } diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index 059ecb4d4d3..49d41ae490f 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1799,9 +1799,31 @@ if (defined('GLOB_BRACE')) { function exec(string $command, &$output = null, int &$result_code = null): string|false {} /** -* @return ($return_array is true ? array|false : object|false) -* @psalm-ignore-falsable-return -*/ + * @psalm-taint-specialize + * @psalm-taint-sink sleep $seconds + */ +function sleep(int $seconds): int {} + +/** + * @psalm-taint-sink sleep $microseconds + */ +function usleep(int $microseconds): void {} + +/** + * @psalm-taint-sink sleep $seconds + * @psalm-taint-sink sleep $nanoseconds + */ +function time_nanosleep(int $seconds, int $nanoseconds): array|bool {} + +/** + * @psalm-taint-sink sleep $timestamp + */ +function time_sleep_until(float $timestamp): bool {} + +/** + * @return ($return_array is true ? array|false : object|false) + * @psalm-ignore-falsable-return + */ function get_browser(?string $user_agent = null, bool $return_array = false): object|array|false {} /** @@ -1823,3 +1845,8 @@ function register_shutdown_function(callable $callback, mixed ...$args): void {} * @psalm-taint-sink callable $callback */ function register_tick_function(callable $callback, mixed ...$args): bool {} + +/** + * @psalm-taint-sink extract $array + */ +function extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = ""): int {} \ No newline at end of file diff --git a/stubs/CoreGenericIterators.phpstub b/stubs/CoreGenericIterators.phpstub index 48abad51dea..2f719131f78 100644 --- a/stubs/CoreGenericIterators.phpstub +++ b/stubs/CoreGenericIterators.phpstub @@ -477,7 +477,7 @@ class EmptyIterator implements Iterator { } /** - * @template-extends SeekableIterator + * @template-extends DirectoryIterator */ class FilesystemIterator extends DirectoryIterator { @@ -523,7 +523,7 @@ class FilesystemIterator extends DirectoryIterator /** - * @template-extends SeekableIterator + * @template-extends FilesystemIterator */ class GlobIterator extends FilesystemIterator implements Countable { /** @@ -774,7 +774,7 @@ class RecursiveArrayIterator extends ArrayIterator implements RecursiveIterator const CHILD_ARRAYS_ONLY = 4 ; /** - * @return RecursiveArrayIterator + * @return ?RecursiveArrayIterator */ public function getChildren() {} diff --git a/stubs/Reflection.phpstub b/stubs/Reflection.phpstub index 68bc4ffd094..368d7e91174 100644 --- a/stubs/Reflection.phpstub +++ b/stubs/Reflection.phpstub @@ -496,7 +496,7 @@ class ReflectionProperty implements Reflector public function isDefault(): bool {} /** - * @return int-mask-of + * @return int-mask-of * @psalm-pure */ public function getModifiers(): int {} diff --git a/stubs/extensions/dom.phpstub b/stubs/extensions/dom.phpstub index b240a8d143d..960ca7927ff 100644 --- a/stubs/extensions/dom.phpstub +++ b/stubs/extensions/dom.phpstub @@ -974,9 +974,14 @@ class DOMXPath public function __construct(DOMDocument $document, bool $registerNodeNS = true) {} + /** + * @return DOMNodeList|false + * @psalm-taint-sink xpath $expression + */ public function evaluate(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): mixed {} /** + * @psalm-taint-sink xpath $expression * @return DOMNodeList|false */ public function query(string $expression, ?DOMNode $contextNode = null, bool $registerNodeNS = true): mixed {} diff --git a/stubs/extensions/intl.phpstub b/stubs/extensions/intl.phpstub new file mode 100644 index 00000000000..99ead8eaeed --- /dev/null +++ b/stubs/extensions/intl.phpstub @@ -0,0 +1,107 @@ +|null|false */ + /** + * @return array|null|false + * @psalm-taint-sink xpath $expression + */ public function xpath(string $expression) {} public function registerXPathNamespace(string $prefix, string $namespace): bool {} diff --git a/stubs/extensions/soap.phpstub b/stubs/extensions/soap.phpstub index 8a3fafa4dcd..dac3ece837c 100644 --- a/stubs/extensions/soap.phpstub +++ b/stubs/extensions/soap.phpstub @@ -1,5 +1,95 @@ [ + 'code' => ' [ 'code' => ' 'InvalidDocblock', ], - 'noCrashOnInvalidClassTemplateAsType' => [ + 'SKIPPED-noCrashOnInvalidClassTemplateAsType' => [ 'code' => ' 'InvalidDocblock', ], - 'noCrashOnInvalidFunctionTemplateAsType' => [ + 'SKIPPED-noCrashOnInvalidFunctionTemplateAsType' => [ 'code' => ' ' $array - * @return non-empty-array + * @return array */ function getArray(array $array): array { if (rand(0, 1)) { @@ -2117,6 +2119,15 @@ function getQueryParams(): array return $queryParams; }', ], + 'AssignListToNonEmptyList' => [ + 'code' => '> $l*/ + $l = []; + $l[] = [];', + 'assertions' => [ + '$l===' => 'non-empty-array>', + ], + ], 'stringIntKeys' => [ 'code' => ' [ 'code' => ' 'float|int', ], ], + 'incrementInLoop' => [ + 'code' => ' [ + '$i' => 'int<0, 10>', + '$j' => 'int<100, 110>', + ], + ], + 'decrementInLoop' => [ + 'code' => ' 0; $i--) { + if (rand(0,1)) { + break; + } + } + for ($j = 110; $j > 100; $j--) { + if (rand(0,1)) { + break; + } + }', + 'assertions' => [ + '$i' => 'int<0, 10>', + '$j' => 'int<100, 110>', + ], + ], 'coalesceFilterOutNullEvenWithTernary' => [ 'code' => ' [ "UndefinedThisPropertyFetch: Instance property A::\$foo is not defined", "MixedReturnStatement: Could not infer a return type", - "MixedInferredReturnType: Could not verify return type 'string' for A::bar", ], ], ], diff --git a/tests/CallableTest.php b/tests/CallableTest.php index f59707faa35..d98d1b57319 100644 --- a/tests/CallableTest.php +++ b/tests/CallableTest.php @@ -1,5 +1,7 @@ [], 'php_version' => '8.0', ], + 'callableArrayTypes' => [ + 'code' => ' [ + '$a' => 'class-string|object', + '$b' => 'string', + '$c' => 'list{class-string|object, string}', + ], + ], 'inferTypeWithNestedTemplatesAndExplicitTypeHint' => [ 'code' => ' 'InvalidFunctionCall', - 'ignored_issues' => ['UndefinedClass', 'MixedInferredReturnType'], + 'ignored_issues' => ['UndefinedClass'], ], 'undefinedCallableMethodFullString' => [ 'code' => ' [], 'ignored_issues' => [ 'UndefinedClass', - 'MixedInferredReturnType', 'InvalidArgument', ], ], @@ -354,7 +355,6 @@ function foo() : D { 'assertions' => [], 'ignored_issues' => [ 'UndefinedClass', - 'MixedInferredReturnType', 'InvalidArgument', ], ], @@ -1461,6 +1461,50 @@ class BazClass implements InterFaceA, InterFaceB {} 'error_message' => 'InheritorViolation', 'ignored_issues' => [], ], + 'duplicateInstanceProperties' => [ + 'code' => <<<'PHP' + 'DuplicateProperty', + 'ignored_issues' => [], + ], + 'duplicateStaticProperties' => [ + 'code' => <<<'PHP' + 'DuplicateProperty', + 'ignored_issues' => [], + ], + 'duplicateMixedProperties' => [ + 'code' => <<<'PHP' + 'DuplicateProperty', + 'ignored_issues' => [], + ], + 'duplicatePropertiesDifferentVisibility' => [ + 'code' => <<<'PHP' + 'DuplicateProperty', + 'ignored_issues' => [], + ], ]; } } diff --git a/tests/ClosureTest.php b/tests/ClosureTest.php index 77900c6ab18..ebdf2532344 100644 --- a/tests/ClosureTest.php +++ b/tests/ClosureTest.php @@ -1,5 +1,7 @@ [ + 'code' => ' [ + 'code' => ' $x; + ', + ], + 'arrowFunctionArg' => [ + 'code' => '}> $existingIssue */ + array_reduce( + $existingIssue, + /** + * @param array{o:int, s:array} $existingIssue + */ + static fn(int $carry, array $existingIssue): int => $carry + $existingIssue["o"], + 0, + ); + ', + ], ]; } @@ -1330,7 +1359,7 @@ function () { );', 'error_message' => 'InvalidArgument', ], - 'undefinedVariableInEncapsedString' => [ + 'undefinedVariableInInterpolatedString' => [ 'code' => ' "$a"; ', @@ -1450,6 +1479,20 @@ public function f(): int { 'ignored_issues' => [], 'php_version' => '8.1', ], + 'returnByReferenceNonVariableInClosure' => [ + 'code' => ' 'NonVariableReferenceReturn', + ], + 'returnByReferenceNonVariableInShortClosure' => [ + 'code' => ' 45; + ', + 'error_message' => 'NonVariableReferenceReturn', + ], ]; } } diff --git a/tests/CodebaseTest.php b/tests/CodebaseTest.php index 254922c2060..e7d1259f329 100644 --- a/tests/CodebaseTest.php +++ b/tests/CodebaseTest.php @@ -1,5 +1,7 @@ file_path = tempnam(sys_get_temp_dir(), 'psalm-test-config'); + $temp_name = tempnam(sys_get_temp_dir(), 'psalm-test-config'); + assert($temp_name !== false); + $this->file_path = $temp_name; } public function tearDown(): void @@ -65,6 +70,8 @@ public function addCanAddPluginClassToExistingPluginsNode(): void $config_file = new ConfigFile((string)getcwd(), $this->file_path); $config_file->addPlugin('a\b\c'); + $file_contents = file_get_contents($this->file_path); + assert($file_contents !== false); $this->assertTrue(static::compareContentWithTemplateAndTrailingLineEnding( ' @@ -73,7 +80,7 @@ public function addCanAddPluginClassToExistingPluginsNode(): void > ', - file_get_contents($this->file_path), + $file_contents, )); } @@ -90,11 +97,13 @@ public function addCanCreateMissingPluginsNode(): void $config_file = new ConfigFile((string)getcwd(), $this->file_path); $config_file->addPlugin('a\b\c'); + $file_contents = file_get_contents($this->file_path); + assert($file_contents !== false); $this->assertTrue(static::compareContentWithTemplateAndTrailingLineEnding( ' ', - file_get_contents($this->file_path), + $file_contents, )); } @@ -110,10 +119,12 @@ public function removeDoesNothingWhenThereIsNoPluginsNode(): void $config_file = new ConfigFile((string)getcwd(), $this->file_path); $config_file->removePlugin('a\b\c'); + $file_contents = file_get_contents($this->file_path); + assert($file_contents !== false); $this->assertSame( $noPlugins, - file_get_contents($this->file_path), + $file_contents, ); } @@ -134,10 +145,12 @@ public function removeKillsEmptyPluginsNode(): void $config_file = new ConfigFile((string)getcwd(), $this->file_path); $config_file->removePlugin('a\b\c'); + $file_contents = file_get_contents($this->file_path); + assert($file_contents !== false); $this->assertXmlStringEqualsXmlString( $noPlugins, - file_get_contents($this->file_path), + $file_contents, ); } @@ -160,10 +173,12 @@ public function removeKillsSpecifiedPlugin(): void $config_file = new ConfigFile((string)getcwd(), $this->file_path); $config_file->removePlugin('a\b\c'); + $file_contents = file_get_contents($this->file_path); + assert($file_contents !== false); $this->assertXmlStringEqualsXmlString( $noPlugins, - file_get_contents($this->file_path), + $file_contents, ); } @@ -195,10 +210,12 @@ public function removeKillsSpecifiedPluginWithOneRemaining(): void $config_file = new ConfigFile((string)getcwd(), $this->file_path); $config_file->removePlugin('a\b\c'); + $file_contents = file_get_contents($this->file_path); + assert($file_contents !== false); $this->assertXmlStringEqualsXmlString( $noPlugins, - file_get_contents($this->file_path), + $file_contents, ); } @@ -268,8 +285,9 @@ protected static function compareContentWithTemplateAndTrailingLineEnding(string $passed = false; foreach ([PHP_EOL, "\n", "\r", "\r\n"] as $eol) { - if (!$passed && $contents === ($expected_template . $eol)) { + if ($contents === ($expected_template . $eol)) { $passed = true; + break; } } diff --git a/tests/Config/ConfigTest.php b/tests/Config/ConfigTest.php index d0aa56b1cc0..913f4fca5fb 100644 --- a/tests/Config/ConfigTest.php +++ b/tests/Config/ConfigTest.php @@ -1,5 +1,7 @@ project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('examples/TemplateScanner.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('examples/TemplateScanner.php'))); } public function testIgnoreProjectDirectory(): void @@ -129,9 +131,9 @@ public function testIgnoreProjectDirectory(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('examples/TemplateScanner.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('examples/TemplateScanner.php'))); } public function testIgnoreMissingProjectDirectory(): void @@ -153,9 +155,9 @@ public function testIgnoreMissingProjectDirectory(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertFalse($config->isInProjectDirs(realpath(__DIR__ . '/../../') . '/does/not/exist/FileAnalyzer.php')); - $this->assertFalse($config->isInProjectDirs(realpath('examples/TemplateScanner.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath(__DIR__ . '/../../') . '/does/not/exist/FileAnalyzer.php')); + $this->assertFalse($config->isInProjectDirs((string) realpath('examples/TemplateScanner.php'))); } /** @@ -202,9 +204,9 @@ public function testIgnoreSymlinkedProjectDirectory(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('tests/AnnotationTest.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('tests/fixtures/symlinktest/a/ignoreme.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('examples/TemplateScanner.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('tests/AnnotationTest.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('tests/fixtures/symlinktest/a/ignoreme.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('examples/TemplateScanner.php'))); $regex = '/^unlink\([^\)]+\): (?:Permission denied|No such file or directory)$/'; $last_error = error_get_last(); @@ -249,10 +251,10 @@ public function testIgnoreWildcardProjectDirectory(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('examples/TemplateScanner.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('examples/TemplateScanner.php'))); } public function testIgnoreRecursiveWildcardProjectDirectory(): void @@ -274,9 +276,9 @@ public function testIgnoreRecursiveWildcardProjectDirectory(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Node/Expr/BinaryOp/VirtualPlus.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Node/Expr/BinaryOp/VirtualPlus.php'))); } public function testIgnoreRecursiveDoubleWildcardProjectFiles(): void @@ -298,9 +300,9 @@ public function testIgnoreRecursiveDoubleWildcardProjectFiles(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); } public function testIgnoreWildcardFiles(): void @@ -322,10 +324,10 @@ public function testIgnoreWildcardFiles(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('examples/TemplateScanner.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('examples/TemplateScanner.php'))); } public function testIgnoreWildcardFilesInWildcardFolder(): void @@ -349,11 +351,11 @@ public function testIgnoreWildcardFilesInWildcardFolder(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); - $this->assertTrue($config->isInProjectDirs(realpath('examples/plugins/StringChecker.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('examples/plugins/StringChecker.php'))); } public function testIgnoreWildcardFilesInAllPossibleWildcardFolders(): void @@ -377,10 +379,10 @@ public function testIgnoreWildcardFilesInAllPossibleWildcardFolders(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Type.php'))); - $this->assertTrue($config->isInProjectDirs(realpath('src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); - $this->assertFalse($config->isInProjectDirs(realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Type.php'))); + $this->assertTrue($config->isInProjectDirs((string) realpath('src/Psalm/Internal/PhpVisitor/ReflectorVisitor.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'))); + $this->assertFalse($config->isInProjectDirs((string) realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); } public function testIssueHandler(): void @@ -404,8 +406,8 @@ public function testIssueHandler(): void $config = $this->project_analyzer->getConfig(); - $this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath(__FILE__))); - $this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->reportIssueInFile('MissingReturnType', (string) realpath(__FILE__))); + $this->assertFalse($config->reportIssueInFile('MissingReturnType', (string) realpath('src/Psalm/Type.php'))); } public function testReportMixedIssues(): void @@ -425,7 +427,7 @@ public function testReportMixedIssues(): void $config = $this->project_analyzer->getConfig(); $this->assertNull($config->show_mixed_issues); - $this->assertTrue($config->reportIssueInFile('MixedArgument', realpath(__FILE__))); + $this->assertTrue($config->reportIssueInFile('MixedArgument', (string) realpath(__FILE__))); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( Config::loadFromXML( @@ -442,7 +444,7 @@ public function testReportMixedIssues(): void $config = $this->project_analyzer->getConfig(); $this->assertFalse($config->show_mixed_issues); - $this->assertFalse($config->reportIssueInFile('MixedArgument', realpath(__FILE__))); + $this->assertFalse($config->reportIssueInFile('MixedArgument', (string) realpath(__FILE__))); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( Config::loadFromXML( @@ -459,7 +461,7 @@ public function testReportMixedIssues(): void $config = $this->project_analyzer->getConfig(); $this->assertNull($config->show_mixed_issues); - $this->assertFalse($config->reportIssueInFile('MixedArgument', realpath(__FILE__))); + $this->assertFalse($config->reportIssueInFile('MixedArgument', (string) realpath(__FILE__))); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( Config::loadFromXML( @@ -476,7 +478,7 @@ public function testReportMixedIssues(): void $config = $this->project_analyzer->getConfig(); $this->assertTrue($config->show_mixed_issues); - $this->assertTrue($config->reportIssueInFile('MixedArgument', realpath(__FILE__))); + $this->assertTrue($config->reportIssueInFile('MixedArgument', (string) realpath(__FILE__))); } public function testGlobalUndefinedFunctionSuppression(): void @@ -533,8 +535,8 @@ public function testMultipleIssueHandlers(): void $config = $this->project_analyzer->getConfig(); - $this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath(__FILE__))); - $this->assertFalse($config->reportIssueInFile('UndefinedClass', realpath(__FILE__))); + $this->assertFalse($config->reportIssueInFile('MissingReturnType', (string) realpath(__FILE__))); + $this->assertFalse($config->reportIssueInFile('UndefinedClass', (string) realpath(__FILE__))); } public function testIssueHandlerWithCustomErrorLevels(): void @@ -613,7 +615,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'info', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -621,7 +623,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'error', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); @@ -629,7 +631,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'error', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('src/psalm.php'), + (string) realpath('src/psalm.php'), ), ); @@ -637,7 +639,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'info', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('examples/TemplateChecker.php'), + (string) realpath('examples/TemplateChecker.php'), ), ); @@ -846,7 +848,7 @@ public function testIssueHandlerSetDynamically(): void 'info', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -854,7 +856,7 @@ public function testIssueHandlerSetDynamically(): void 'error', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); @@ -862,7 +864,7 @@ public function testIssueHandlerSetDynamically(): void 'error', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('src/psalm.php'), + (string) realpath('src/psalm.php'), ), ); @@ -870,7 +872,7 @@ public function testIssueHandlerSetDynamically(): void 'info', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('examples/TemplateChecker.php'), + (string) realpath('examples/TemplateChecker.php'), ), ); @@ -1022,7 +1024,7 @@ public function testIssueHandlerOverride(): void 'info', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -1030,14 +1032,14 @@ public function testIssueHandlerOverride(): void 'error', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); $this->assertSame( 'suppress', $config->getReportingLevelForFile( 'UndefinedClass', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); } @@ -1081,7 +1083,7 @@ public function testIssueHandlerSafeOverride(): void 'error', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -1089,14 +1091,14 @@ public function testIssueHandlerSafeOverride(): void 'info', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); $this->assertSame( 'info', $config->getReportingLevelForFile( 'UndefinedClass', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); } @@ -1170,7 +1172,7 @@ public function testThing(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1205,7 +1207,7 @@ public function testValidThrowInvalidCatch(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1253,7 +1255,7 @@ public function testInvalidThrowValidCatch(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1304,7 +1306,7 @@ public function testValidThrowValidCatch(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1334,6 +1336,10 @@ function handleThrow(I $e) : void { public function testModularConfig(): void { + // hack to isolate Psalm from PHPUnit arguments + global $argv; + $argv = []; + $root = __DIR__ . '/../fixtures/ModularConfig'; $config = Config::loadFromXMLFile($root . '/psalm.xml', $root); $this->assertEquals( @@ -1377,7 +1383,7 @@ public function testGlobals(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1475,7 +1481,7 @@ public function testIgnoreExceptions(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1541,7 +1547,7 @@ public function testNotIgnoredException(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1758,14 +1764,14 @@ public function testTypeStatsForFileReporting(): void $config = $this->project_analyzer->getConfig(); - $this->assertFalse($config->reportTypeStatsForFile(realpath('src/Psalm/Config') . DIRECTORY_SEPARATOR)); - $this->assertTrue($config->reportTypeStatsForFile(realpath('src/Psalm/Internal') . DIRECTORY_SEPARATOR)); - $this->assertTrue($config->reportTypeStatsForFile(realpath('src/Psalm/Issue') . DIRECTORY_SEPARATOR)); - $this->assertTrue($config->reportTypeStatsForFile(realpath('src/Psalm/Node') . DIRECTORY_SEPARATOR)); - $this->assertTrue($config->reportTypeStatsForFile(realpath('src/Psalm/Plugin') . DIRECTORY_SEPARATOR)); - $this->assertTrue($config->reportTypeStatsForFile(realpath('src/Psalm/Progress') . DIRECTORY_SEPARATOR)); - $this->assertTrue($config->reportTypeStatsForFile(realpath('src/Psalm/Report') . DIRECTORY_SEPARATOR)); - $this->assertTrue($config->reportTypeStatsForFile(realpath('src/Psalm/SourceControl') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->reportTypeStatsForFile((string) realpath('src/Psalm/Config') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->reportTypeStatsForFile((string) realpath('src/Psalm/Internal') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->reportTypeStatsForFile((string) realpath('src/Psalm/Issue') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->reportTypeStatsForFile((string) realpath('src/Psalm/Node') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->reportTypeStatsForFile((string) realpath('src/Psalm/Plugin') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->reportTypeStatsForFile((string) realpath('src/Psalm/Progress') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->reportTypeStatsForFile((string) realpath('src/Psalm/Report') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->reportTypeStatsForFile((string) realpath('src/Psalm/SourceControl') . DIRECTORY_SEPARATOR)); } public function testStrictTypesForFileReporting(): void @@ -1791,14 +1797,14 @@ public function testStrictTypesForFileReporting(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->useStrictTypesForFile(realpath('src/Psalm/Config') . DIRECTORY_SEPARATOR)); - $this->assertFalse($config->useStrictTypesForFile(realpath('src/Psalm/Internal') . DIRECTORY_SEPARATOR)); - $this->assertFalse($config->useStrictTypesForFile(realpath('src/Psalm/Issue') . DIRECTORY_SEPARATOR)); - $this->assertFalse($config->useStrictTypesForFile(realpath('src/Psalm/Node') . DIRECTORY_SEPARATOR)); - $this->assertFalse($config->useStrictTypesForFile(realpath('src/Psalm/Plugin') . DIRECTORY_SEPARATOR)); - $this->assertFalse($config->useStrictTypesForFile(realpath('src/Psalm/Progress') . DIRECTORY_SEPARATOR)); - $this->assertFalse($config->useStrictTypesForFile(realpath('src/Psalm/Report') . DIRECTORY_SEPARATOR)); - $this->assertFalse($config->useStrictTypesForFile(realpath('src/Psalm/SourceControl') . DIRECTORY_SEPARATOR)); + $this->assertTrue($config->useStrictTypesForFile((string) realpath('src/Psalm/Config') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->useStrictTypesForFile((string) realpath('src/Psalm/Internal') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->useStrictTypesForFile((string) realpath('src/Psalm/Issue') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->useStrictTypesForFile((string) realpath('src/Psalm/Node') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->useStrictTypesForFile((string) realpath('src/Psalm/Plugin') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->useStrictTypesForFile((string) realpath('src/Psalm/Progress') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->useStrictTypesForFile((string) realpath('src/Psalm/Report') . DIRECTORY_SEPARATOR)); + $this->assertFalse($config->useStrictTypesForFile((string) realpath('src/Psalm/SourceControl') . DIRECTORY_SEPARATOR)); } public function testConfigFileWithXIncludeWithoutFallbackShouldThrowException(): void @@ -1852,7 +1858,7 @@ public function testConfigFileWithXIncludeWithFallback(): void $config = $this->project_analyzer->getConfig(); - $this->assertFalse($config->reportIssueInFile('MixedAssignment', realpath('src/Psalm/Type.php'))); + $this->assertFalse($config->reportIssueInFile('MixedAssignment', (string) realpath('src/Psalm/Type.php'))); } public function testConfigFileWithWildcardPathIssueHandler(): void @@ -1881,14 +1887,14 @@ public function testConfigFileWithWildcardPathIssueHandler(): void $config = $this->project_analyzer->getConfig(); - $this->assertTrue($config->reportIssueInFile('MissingReturnType', realpath(__FILE__))); - $this->assertTrue($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Type.php'))); - $this->assertTrue($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); + $this->assertTrue($config->reportIssueInFile('MissingReturnType', (string) realpath(__FILE__))); + $this->assertTrue($config->reportIssueInFile('MissingReturnType', (string) realpath('src/Psalm/Type.php'))); + $this->assertTrue($config->reportIssueInFile('MissingReturnType', (string) realpath('src/Psalm/Internal/Analyzer/Statements/ReturnAnalyzer.php'))); - $this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Node/Expr/BinaryOp/VirtualPlus.php'))); - $this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php'))); - $this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Internal/Type/TypeAlias.php'))); - $this->assertFalse($config->reportIssueInFile('MissingReturnType', realpath('src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php'))); + $this->assertFalse($config->reportIssueInFile('MissingReturnType', (string) realpath('src/Psalm/Node/Expr/BinaryOp/VirtualPlus.php'))); + $this->assertFalse($config->reportIssueInFile('MissingReturnType', (string) realpath('src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOp/OrAnalyzer.php'))); + $this->assertFalse($config->reportIssueInFile('MissingReturnType', (string) realpath('src/Psalm/Internal/Type/TypeAlias.php'))); + $this->assertFalse($config->reportIssueInFile('MissingReturnType', (string) realpath('src/Psalm/Internal/Type/TypeAlias/ClassTypeAlias.php'))); } /** @@ -1912,7 +1918,7 @@ public function testConfigWarnsAboutDeprecatedWayToLoadStubsButLoadsTheStub(): v $config->visitStubFiles($codebase); - $this->assertContains(realpath('stubs/extensions/apcu.phpstub'), $config->internal_stubs); + $this->assertContains((string) realpath('stubs/extensions/apcu.phpstub'), $config->internal_stubs); $this->assertContains( 'Psalm 6 will not automatically load stubs for ext-apcu. You should explicitly enable or disable this ext in composer.json or Psalm config.', $config->config_warnings, @@ -1943,7 +1949,7 @@ public function testConfigWithDisableExtensionsDoesNotLoadExtensionStubsAndHides $config->visitStubFiles($codebase); - $this->assertNotContains(realpath('stubs/extensions/apcu.phpstub'), $config->internal_stubs); + $this->assertNotContains((string) realpath('stubs/extensions/apcu.phpstub'), $config->internal_stubs); $this->assertNotContains( 'Psalm 6 will not automatically load stubs for ext-apcu. You should explicitly enable or disable this ext in composer.json or Psalm config.', $config->internal_stubs, diff --git a/tests/Config/CreatorTest.php b/tests/Config/CreatorTest.php index 38015e8df35..2928e417c88 100644 --- a/tests/Config/CreatorTest.php +++ b/tests/Config/CreatorTest.php @@ -1,5 +1,7 @@ project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -131,7 +137,7 @@ public function testStringAnalyzerPluginWithClassConstant(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( @@ -175,7 +181,7 @@ public function testStringAnalyzerPluginWithClassConstantConcat(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -217,7 +223,7 @@ public function testEchoAnalyzerPluginWithJustHtml(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -255,7 +261,7 @@ public function testEchoAnalyzerPluginWithUnescapedConcatenatedString(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -292,7 +298,7 @@ public function testEchoAnalyzerPluginWithUnescapedString(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -330,7 +336,7 @@ public function testFileAnalyzerPlugin(): void $this->assertCount(1, $codebase->config->eventDispatcher->before_file_checks); $this->assertCount(1, $codebase->config->eventDispatcher->after_file_checks); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -373,7 +379,7 @@ public function testFloatCheckerPlugin(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -411,7 +417,7 @@ public function testFloatCheckerPluginIssueSuppressionByConfig(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -444,7 +450,7 @@ public function testFloatCheckerPluginIssueSuppressionByDocblock(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -546,7 +552,7 @@ public function testPropertyProviderHooks(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -585,7 +591,7 @@ public function testMethodProviderHooksValidArg(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -646,7 +652,7 @@ public function testFunctionProviderHooks(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -682,7 +688,7 @@ public function testPropertyProviderHooksInvalidAssignment(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -723,7 +729,7 @@ public function testMethodProviderHooksInvalidArg(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -766,7 +772,7 @@ public function testFunctionProviderHooksInvalidArg(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -899,7 +905,7 @@ public static function afterEveryFunctionCallAnalysis(AfterEveryFunctionCallAnal $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); $this->project_analyzer->getCodebase()->config->eventDispatcher->after_every_function_checks[] = get_class($plugin); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -939,7 +945,7 @@ public function testRemoveTaints(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1011,7 +1017,7 @@ public function testFunctionDynamicStorageProviderHook(): void $this->project_analyzer->getCodebase()->config->initializePlugins($this->project_analyzer); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, diff --git a/tests/ConstValuesTest.php b/tests/ConstValuesTest.php index 7ff3c51f7e1..564867fdabf 100644 --- a/tests/ConstValuesTest.php +++ b/tests/ConstValuesTest.php @@ -1,5 +1,7 @@ testConfig->ensure_array_int_offsets_exist = true; - // $file_path = getcwd() . '/src/somefile.php'; + // $file_path = (string) getcwd() . '/src/somefile.php'; // $this->addFile( // $file_path, @@ -49,8 +51,8 @@ class ConstantTest extends TestCase public function testUseObjectConstant(): void { - $file1 = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file1.php'; - $file2 = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file2.php'; + $file1 = (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file1.php'; + $file2 = (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'file2.php'; $this->addFile( $file1, diff --git a/tests/CoreStubsTest.php b/tests/CoreStubsTest.php index c388fc12913..f45d40c9b5c 100644 --- a/tests/CoreStubsTest.php +++ b/tests/CoreStubsTest.php @@ -1,5 +1,7 @@ map(); }', ], + 'inheritCorrectParamOnTypeChange' => [ + 'code' => '|int $className */ + public function a(array|int $className): int + { + return 0; + } + } + + class B extends A + { + /** @param array|int|bool $className */ + public function a(array|int|bool $className): int + { + return 0; + } + } + + print_r((new A)->a(1)); + print_r((new B)->a(true)); + ', + 'assertions' => [], + 'ignored_issues' => [], + 'php_version' => '8.0', + ], ]; } diff --git a/tests/DocumentationTest.php b/tests/DocumentationTest.php index 95b2ad3c1b8..7745cfb07f9 100644 --- a/tests/DocumentationTest.php +++ b/tests/DocumentationTest.php @@ -1,5 +1,7 @@ inner->toString(); } - /** - * @param mixed $other - */ - protected function matches($other): bool + protected function matches(mixed $other): bool { return $this->inner->matches($other); } - /** - * @param mixed $other - */ - protected function failureDescription($other): string + protected function failureDescription(mixed $other): string { return $this->exporter()->shortenedExport($other) . ' ' . $this->toString(); } @@ -459,13 +459,15 @@ public function testIssuesIndex(): void return $matches[1]; }, $issues_index_contents); + $dir_contents = scandir($issues_dir); + assert($dir_contents !== false); $issue_files = array_filter(array_map(function (string $issue_file) { if ($issue_file === "." || $issue_file === "..") { return false; } $this->assertStringEndsWith(".md", $issue_file, "Invalid file in issues documentation: $issue_file"); return substr($issue_file, 0, strlen($issue_file) - 3); - }, scandir($issues_dir))); + }, $dir_contents)); $unlisted_issues = array_diff($issue_files, $issues_index_list); $this->assertEmpty($unlisted_issues, "Issue documentation missing from issues.md: " . implode(", ", $unlisted_issues)); diff --git a/tests/EndToEnd/DestructiveAutoloaderTest.php b/tests/EndToEnd/DestructiveAutoloaderTest.php index 400efeec9d6..74a8a59e6de 100644 --- a/tests/EndToEnd/DestructiveAutoloaderTest.php +++ b/tests/EndToEnd/DestructiveAutoloaderTest.php @@ -1,5 +1,7 @@ assertSame(2, $result['CODE']); } + public function testPsalmSetBaseline(): void + { + $this->runPsalmInit(1); + $this->runPsalm(['--set-baseline'], self::$tmpDir, true); + + $this->assertSame(0, $this->runPsalm([], self::$tmpDir)['CODE']); + } + + public function testPsalmSetBaselineWithArgument(): void + { + $this->runPsalmInit(1); + $this->runPsalm(['--set-baseline=psalm-custom-baseline.xml'], self::$tmpDir, true); + + $this->assertSame(0, $this->runPsalm([], self::$tmpDir)['CODE']); + } + public function testTaintingWithoutInit(): void { $result = $this->runPsalm(['--taint-analysis'], self::$tmpDir, true, false); @@ -207,7 +227,7 @@ public function testTaintGraphDumping(): void $result = $this->runPsalm( [ '--taint-analysis', - '--dump-taint-graph='.self::$tmpDir.'/taints.dot', + '--dump-taint-graph=' . self::$tmpDir . '/taints.dot', ], self::$tmpDir, true, @@ -216,7 +236,7 @@ public function testTaintGraphDumping(): void $this->assertSame(2, $result['CODE']); $this->assertFileEquals( __DIR__ . '/../fixtures/expected_taint_graph.dot', - self::$tmpDir.'/taints.dot', + self::$tmpDir . '/taints.dot', ); } @@ -224,8 +244,9 @@ public function testLegacyConfigWithoutresolveFromConfigFile(): void { $this->runPsalmInit(1); $psalmXmlContent = file_get_contents(self::$tmpDir . '/psalm.xml'); + assert($psalmXmlContent !== false); $count = 0; - $psalmXmlContent = preg_replace('/resolveFromConfigFile="true"/', 'resolveFromConfigFile="false"', $psalmXmlContent, -1, $count); + $psalmXmlContent = (string) preg_replace('/resolveFromConfigFile="true"/', 'resolveFromConfigFile="false"', $psalmXmlContent, -1, $count); $this->assertEquals(1, $count); file_put_contents(self::$tmpDir . '/src/psalm.xml', $psalmXmlContent); @@ -241,7 +262,8 @@ public function testPsalmWithNoProgressDoesNotProduceOutputOnStderr(): void $this->runPsalmInit(); $psalmXml = file_get_contents(self::$tmpDir . '/psalm.xml'); - $psalmXml = preg_replace('/findUnusedCode="(true|false)"/', '', $psalmXml, 1); + assert($psalmXml !== false); + $psalmXml = (string) preg_replace('/findUnusedCode="(true|false)"/', '', $psalmXml, 1); file_put_contents(self::$tmpDir . '/psalm.xml', $psalmXml); $result = $this->runPsalm(['--no-progress'], self::$tmpDir); @@ -258,12 +280,13 @@ private function runPsalmInit(?int $level = null, ?string $php_version = null): if ($level) { $args[] = 'src'; - $args[] = (string) $level; + $args[] = (string)$level; } $ret = $this->runPsalm($args, self::$tmpDir, false, false); $psalm_config_contents = file_get_contents(self::$tmpDir . '/psalm.xml'); + assert($psalm_config_contents !== false); $psalm_config_contents = str_replace( 'errorLevel="1"', 'errorLevel="1" ' @@ -282,6 +305,7 @@ private function runPsalmInit(?int $level = null, ?string $php_version = null): private static function recursiveRemoveDirectory(string $src): void { $dir = opendir($src); + assert($dir !== false); while (false !== ($file = readdir($dir))) { if (($file !== '.') && ($file !== '..')) { $full = $src . DIRECTORY_SEPARATOR . $file; diff --git a/tests/EndToEnd/PsalmRunnerTrait.php b/tests/EndToEnd/PsalmRunnerTrait.php index 92900ec9b39..950f02190e6 100644 --- a/tests/EndToEnd/PsalmRunnerTrait.php +++ b/tests/EndToEnd/PsalmRunnerTrait.php @@ -1,5 +1,7 @@ [], 'php_version' => '8.1', ], + 'classStringAsBackedEnumValue' => [ + 'code' => <<<'PHP' + value; + noop($foo); + noop(FooEnum::Foo->value); + PHP, + 'assertions' => [], + 'ignored_issues' => [], + 'php_version' => '8.1', + ], 'backedEnumCaseValueFromClassConstant' => [ 'code' => <<<'PHP' [], 'php_version' => '8.1', ], + 'stringBackedEnumCaseValueFromStringGlobalConstant' => [ + 'code' => ' [], + 'ignored_issues' => [], + 'php_version' => '8.1', + ], + 'intBackedEnumCaseValueFromIntGlobalConstant' => [ + 'code' => ' [], + 'ignored_issues' => [], + 'php_version' => '8.1', + ], 'allowPropertiesOnIntersectionsWithEnumInterfaces' => [ 'code' => <<<'PHP' [], 'php_version' => '8.1', ], - 'stringBackedEnumCaseValueFromStringGlobalConstant' => [ - 'code' => ' [], - 'ignored_issues' => [], - 'php_version' => '8.1', - ], - 'intBackedEnumCaseValueFromIntGlobalConstant' => [ - 'code' => ' [], - 'ignored_issues' => [], - 'php_version' => '8.1', - ], 'enumWithCasesReferencingClassConstantsWhereClassIsDefinedAfterTheEnum' => [ 'code' => <<<'PHP' foo bar - @@ -57,7 +57,6 @@ public function testLoadShouldParseXmlBaselineToPhpArray(): void $expectedParsedBaseline = [ 'sample/sample-file.php' => [ 'MixedAssignment' => ['o' => 2, 's' => ['foo', 'bar']], - 'InvalidReturnStatement' => ['o' => 1, 's' => []], ], 'sample/sample-file2.php' => [ 'PossiblyUnusedMethod' => ['o' => 2, 's' => ['foo', 'bar']], @@ -393,11 +392,18 @@ public function testUpdateShouldRemoveExistingIssuesWithoutAddingNewOnes(): void bar bat - + + Test + - - + + bar + baz + + + bar + @@ -531,7 +537,6 @@ public function testAddingACommentInBaselineDoesntTriggerNotice(): void foo bar - @@ -546,7 +551,6 @@ public function testAddingACommentInBaselineDoesntTriggerNotice(): void $expectedParsedBaseline = [ 'sample/sample-file.php' => [ 'MixedAssignment' => ['o' => 2, 's' => ['foo', 'bar']], - 'InvalidReturnStatement' => ['o' => 1, 's' => []], ], 'sample/sample-file2.php' => [ 'PossiblyUnusedMethod' => ['o' => 2, 's' => ['foo', 'bar']], diff --git a/tests/ExpressionTest.php b/tests/ExpressionTest.php index 97c57c9db96..5c63430829f 100644 --- a/tests/ExpressionTest.php +++ b/tests/ExpressionTest.php @@ -1,5 +1,7 @@ getTestName(), 'SKIPPED-') !== false) { $this->markTestSkipped(); @@ -84,7 +86,7 @@ public function testPartialAstDiff( array $same_methods, array $same_signatures, array $changed_methods, - array $diff_map_offsets + array $diff_map_offsets, ): void { if (strpos($this->getTestName(), 'SKIPPED-') !== false) { $this->markTestSkipped(); diff --git a/tests/FileManipulation/ClassConstantMoveTest.php b/tests/FileManipulation/ClassConstantMoveTest.php index f06239abb0a..3e22aa14a0b 100644 --- a/tests/FileManipulation/ClassConstantMoveTest.php +++ b/tests/FileManipulation/ClassConstantMoveTest.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { diff --git a/tests/FileManipulation/ClassMoveTest.php b/tests/FileManipulation/ClassMoveTest.php index e49e0cf87f8..c2f5c8ee7a5 100644 --- a/tests/FileManipulation/ClassMoveTest.php +++ b/tests/FileManipulation/ClassMoveTest.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { diff --git a/tests/FileManipulation/FileManipulationTestCase.php b/tests/FileManipulation/FileManipulationTestCase.php index de361f6ffd2..06941283881 100644 --- a/tests/FileManipulation/FileManipulationTestCase.php +++ b/tests/FileManipulation/FileManipulationTestCase.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { diff --git a/tests/FileManipulation/ImmutableAnnotationAdditionTest.php b/tests/FileManipulation/ImmutableAnnotationAdditionTest.php index 926cef22aad..0e3083911c4 100644 --- a/tests/FileManipulation/ImmutableAnnotationAdditionTest.php +++ b/tests/FileManipulation/ImmutableAnnotationAdditionTest.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { diff --git a/tests/FileManipulation/MissingPropertyTypeTest.php b/tests/FileManipulation/MissingPropertyTypeTest.php index f32d8c2562c..2bd15250b48 100644 --- a/tests/FileManipulation/MissingPropertyTypeTest.php +++ b/tests/FileManipulation/MissingPropertyTypeTest.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { diff --git a/tests/FileManipulation/ParamNameMismatchTest.php b/tests/FileManipulation/ParamNameMismatchTest.php index 3dfd82d778a..186885a4309 100644 --- a/tests/FileManipulation/ParamNameMismatchTest.php +++ b/tests/FileManipulation/ParamNameMismatchTest.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { diff --git a/tests/FileManipulation/PureAnnotationAdditionTest.php b/tests/FileManipulation/PureAnnotationAdditionTest.php index 3434481ad4f..2b44581a82e 100644 --- a/tests/FileManipulation/PureAnnotationAdditionTest.php +++ b/tests/FileManipulation/PureAnnotationAdditionTest.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { diff --git a/tests/FileUpdates/AnalyzedMethodTest.php b/tests/FileUpdates/AnalyzedMethodTest.php index e965084ae2b..598efeb6189 100644 --- a/tests/FileUpdates/AnalyzedMethodTest.php +++ b/tests/FileUpdates/AnalyzedMethodTest.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'SKIPPED-') !== false) { @@ -113,7 +115,7 @@ public function providerTestValidUpdates(): array return [ 'basicRequire' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::foofoo' => 1, 'foo\a::barbar' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, 'foo\b::noreturntype' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::barbar' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::bar' => 1, 'foo\b::noreturntype' => 1, ], @@ -194,14 +196,14 @@ public function noReturnType() {} ], 'invalidateAfterPropertyChange' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::bar' => 1, ], ], ], 'invalidateAfterStaticPropertyChange' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::bar' => 1, ], ], ], 'invalidateAfterStaticFlipPropertyChange' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::bar' => 1, ], ], ], 'invalidateAfterConstantChange' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::bar' => 1, ], ], ], 'dontInvalidateTraitMethods' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::barbar&foo\t::barbar' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::foofoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, 'foo\b::noreturntype' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::barbar&foo\t::barbar' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::bar' => 1, 'foo\b::noreturntype' => 1, ], @@ -504,7 +506,7 @@ public function barBar(): string { ], 'invalidateTraitMethodsWhenTraitRemoved' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::barbar&foo\t::barbar' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::foofoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], ], ], 'invalidateTraitMethodsWhenTraitReplaced' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::barbar&foo\t::barbar' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::foofoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], ], ], 'invalidateTraitMethodsWhenMethodChanged' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::barbar&foo\t::barbar' => 1, 'foo\a::bat&foo\t::bat' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::foofoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::foo' => 1, 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::bat&foo\t::bat' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], ], ], 'invalidateTraitMethodsWhenMethodSuperimposed' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'barBar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::barbar&foo\t::barbar' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [ 'foo\b::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php' => [], ], ], 'dontInvalidateConstructor' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::setfoo' => 1, 'foo\a::reallysetfoo' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::setfoo' => 1, 'foo\a::reallysetfoo' => 1, @@ -877,7 +879,7 @@ private function reallySetFoo() : void { ], 'invalidateConstructorWhenDependentMethodChanges' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::setfoo' => 1, 'foo\a::reallysetfoo' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::setfoo' => 1, ], ], ], 'invalidateConstructorWhenDependentMethodInSubclassChanges' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 1, 'foo\a::setfoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ 'foo\achild::setfoo' => 1, 'foo\achild::reallysetfoo' => 1, 'foo\achild::__construct' => 2, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 1, 'foo\a::setfoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ 'foo\achild::setfoo' => 1, ], ], ], 'invalidateConstructorWhenDependentMethodInSubclassChanges2' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo = "bar"; } }', - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo = "baz"; } }', - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::setfoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ 'foo\achild::__construct' => 2, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [], ], ], 'invalidateConstructorWhenDependentTraitMethodChanges' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'setFoo(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'setFoo(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [ 'foo\a::setfoo&foo\t::setfoo' => 1, ], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'T.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [], ], ], 'rescanPropertyAssertingMethod' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, ], ], @@ -1182,7 +1184,7 @@ public function bar() : void { ], 'noChangeAfterSyntaxError' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::bar' => 1, ], @@ -1224,7 +1226,7 @@ public function bar() : void { ], 'nothingBeforeSyntaxError' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::bar' => 1, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, 'foo\a::bar' => 1, ], @@ -1266,7 +1268,7 @@ public function bar() : void { ], 'modifyPropertyOfChildClass' => [ 'start_files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'b = $b; } }', - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'b = $b; } }', - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, ], - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [ 'foo\achild::__construct' => 2, ], ], 'unaffected_analyzed_methods' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php' => [ 'foo\a::__construct' => 2, ], - getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [], + (string) getcwd() . DIRECTORY_SEPARATOR . 'AChild.php' => [], ], ], ]; diff --git a/tests/FileUpdates/CachedStorageTest.php b/tests/FileUpdates/CachedStorageTest.php index 08936b6085b..a41a33b86bb 100644 --- a/tests/FileUpdates/CachedStorageTest.php +++ b/tests/FileUpdates/CachedStorageTest.php @@ -1,5 +1,7 @@ project_analyzer->getCodebase(); $vendor_files = [ - getcwd() . DIRECTORY_SEPARATOR . 'V1.php' => ' ' ' ' ' ' ' 'project_analyzer->getCodebase()->diff_methods = true; $this->project_analyzer->getCodebase()->reportUnusedCode(); @@ -108,17 +110,17 @@ public function providerTestInvalidUpdates(): array 'invalidateParentCaller' => [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' 'bar();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' 'existingMethod();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' 'newMethod();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'foo;', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'foo;', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo;', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo;', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo("hello");', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'bar();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo;', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'bar();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' [ 'file_stages' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'project_analyzer->getCodebase()->diff_methods = true; @@ -110,7 +112,7 @@ public function providerTestErrorFix(): array 'fixMissingColonSyntaxError' => [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'hasMethod($method); }', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'C.php' => ' ' ' 'hasMethod($method); @@ -416,14 +418,14 @@ function hasMethod(object $input, string $method): bool { 'missingConstructorForTwoVars' => [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ 'files' => [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' 'codebase; $codebase->diff_methods = true; @@ -179,7 +181,7 @@ public function providerTestErrorFix(): array 'fixMissingColonSyntaxError' => [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bat(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bat(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'bar(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' 'foo();', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' ' 'foo();', - getcwd() . DIRECTORY_SEPARATOR . 'T.php' => ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' ' ' ' ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' 'foo();', @@ -1781,7 +1783,7 @@ public function bar() : void {} 'usedMethodWithNoAffectedConstantChanges' => [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' 'doFoo();', ], [ - getcwd() . DIRECTORY_SEPARATOR . 'src' . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' 'doFoo();', @@ -1840,7 +1842,7 @@ public function doFoo() : void { 'syntaxErrorFixed' => [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ [ [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' ' 'addFile( $file_path, @@ -107,7 +109,7 @@ public function testForbiddenCodeFunctionViaFunctions(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -128,7 +130,7 @@ public function testAllowedPrintFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -155,7 +157,7 @@ public function testForbiddenPrintFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -176,7 +178,7 @@ public function testAllowedVarExportFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -204,7 +206,7 @@ public function testForbiddenVarExportFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -231,7 +233,7 @@ public function testNoExceptionWithMatchingNameButDifferentNamespace(): void XML, ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, <<<'PHP' @@ -266,7 +268,7 @@ public function testForbiddenEmptyFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -293,7 +295,7 @@ public function testForbiddenExitFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -321,7 +323,7 @@ public function testForbiddenDieFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -349,7 +351,7 @@ public function testForbiddenEvalExpression(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, diff --git a/tests/FunctionCallTest.php b/tests/FunctionCallTest.php index b928a42cecf..b551896681e 100644 --- a/tests/FunctionCallTest.php +++ b/tests/FunctionCallTest.php @@ -1,5 +1,7 @@ 'false|int|null', '$porte' => 'false|int|null', ], - 'ignored_issues' => ['MixedReturnStatement', 'MixedInferredReturnType'], + 'ignored_issues' => ['MixedReturnStatement'], ], 'parseUrlComponent' => [ 'code' => 'project_analyzer->getCodebase(); @@ -64,7 +66,7 @@ public function testInvalidInclude( array $files, array $files_to_check, string $error_message, - array $directories = [] + array $directories = [], ): void { if (strpos($this->getTestName(), 'SKIPPED-') !== false) { $this->markTestSkipped(); @@ -107,7 +109,7 @@ public function providerTestValidIncludes(): array return [ 'basicRequire' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' 'fooFoo(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireSingleStringType' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' 'fooFoo(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'nestedRequire' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file3.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], ], 'requireNamespace' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireFunction' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'namespacedRequireFunction' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireConstant' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'requireNamespacedWithUse' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'noInfiniteRequireLoop' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', - getcwd() . DIRECTORY_SEPARATOR . 'file3.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], ], 'analyzeAllClasses' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'loopWithInterdependencies' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'variadicArgs' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'globalIncludedVar' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'returnNamespacedFunctionCallType' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file3.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], ], 'functionUsedElsewhere' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'closureInIncludedFile' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'hoistConstants' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'hoist_constants' => true, ], 'duplicateClasses' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' 'aa(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' 'dd(); } }', ], 'files_to_check' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'hoist_constants' => false, 'ignored_issues' => ['DuplicateClass'], ], 'duplicateClassesProperty' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'hoist_constants' => false, 'ignored_issues' => ['DuplicateClass', 'MissingPropertyType'], ], 'functionsDefined' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'index.php' => ' ' ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'index.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'index.php', ], ], 'suppressMissingFile' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], ], 'nestedParentFile' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'c' . DIRECTORY_SEPARATOR . 'd' . DIRECTORY_SEPARATOR . 'script.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'c' . DIRECTORY_SEPARATOR . 'd' . DIRECTORY_SEPARATOR . 'script.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'b' . DIRECTORY_SEPARATOR . 'c' . DIRECTORY_SEPARATOR . 'd' . DIRECTORY_SEPARATOR . 'script.php', ], ], 'undefinedMethodAfterInvalidRequire' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'returnValue' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], ], 'noCrash' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'classes.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'user.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'user.php', ], ], 'pathStartingWithDot' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'test_1.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'test_1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'test_1.php', ], ], ]; @@ -668,7 +670,7 @@ public function providerTestInvalidIncludes(): array return [ 'undefinedMethodInRequire' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' 'fooFo(); } }', - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'UndefinedMethod', ], 'requireFunctionWithStrictTypes' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidArgument', ], 'requireFunctionWithStrictTypesInClass' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidArgument', ], 'requireFunctionWithWeakTypes' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidScalarArgument', ], 'requireFunctionWithStrictTypesButDocblockType' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidArgument', ], 'namespacedRequireFunction' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'UndefinedFunction', ], 'globalIncludedIncorrectVar' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], 'error_message' => 'UndefinedVariable', ], 'invalidTraitFunctionReturnInUncheckedFile' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'InvalidReturnType', ], 'invalidDoubleNestedTraitFunctionReturnInUncheckedFile' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file3.php' => ' ' ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file3.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file3.php', ], 'error_message' => 'InvalidReturnType', ], 'invalidTraitFunctionMissingNestedUse' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'A.php', - getcwd() . DIRECTORY_SEPARATOR . 'B.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'A.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'B.php', ], 'error_message' => 'UndefinedTrait - A.php:3:33', ], 'SKIPPED-noHoistConstants' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file1.php', ], 'error_message' => 'UndefinedConstant', ], 'undefinedMethodAfterInvalidRequire' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'file2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'file2.php', ], 'error_message' => 'UndefinedFunction', ], 'pathStartingWithDot' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'test_1.php' => ' ' ' ' [ - getcwd() . DIRECTORY_SEPARATOR . 'test_1.php', - getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'test_2.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'test_1.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'a' . DIRECTORY_SEPARATOR . 'test_2.php', ], 'error_message' => 'MissingFile', ], 'directoryPath' => [ 'files' => [ - getcwd() . DIRECTORY_SEPARATOR . 'test.php' => ' ' [getcwd() . DIRECTORY_SEPARATOR . 'test.php'], + 'files_to_check' => [(string) getcwd() . DIRECTORY_SEPARATOR . 'test.php'], 'error_message' => 'MissingFile', - 'directories' => [getcwd() . DIRECTORY_SEPARATOR], + 'directories' => [(string) getcwd() . DIRECTORY_SEPARATOR], ], ]; } diff --git a/tests/IntRangeTest.php b/tests/IntRangeTest.php index 9e7f3388413..9a1d5cd05ff 100644 --- a/tests/IntRangeTest.php +++ b/tests/IntRangeTest.php @@ -1,5 +1,7 @@ [$function, $entry]; + yield "$function: " . (string) json_encode($entry) => [$function, $entry]; } } @@ -634,7 +636,6 @@ private function assertParameter(array $normalizedEntry, ReflectionParameter $pa public function assertEntryReturnType(ReflectionFunctionAbstract $function, string $entryReturnType): void { if (version_compare(PHP_VERSION, '8.1.0', '>=')) { - /** @var ReflectionType|null $expectedType */ $expectedType = $function->hasTentativeReturnType() ? $function->getTentativeReturnType() : $function->getReturnType(); } else { $expectedType = $function->getReturnType(); diff --git a/tests/Internal/Codebase/MethodGetCompletionItemsForClassishThingTest.php b/tests/Internal/Codebase/MethodGetCompletionItemsForClassishThingTest.php index 9519553237b..82e55564204 100644 --- a/tests/Internal/Codebase/MethodGetCompletionItemsForClassishThingTest.php +++ b/tests/Internal/Codebase/MethodGetCompletionItemsForClassishThingTest.php @@ -281,6 +281,7 @@ class A { 'magicObjProp2', 'magicObjMethod', + //'magicStaticMethod', 'publicObjProp', 'protectedObjProp', @@ -361,6 +362,7 @@ abstract class A { 'magicObjProp2', 'magicObjMethod', + //'magicStaticMethod', 'publicObjProp', 'protectedObjProp', @@ -438,6 +440,7 @@ class A extends C { 'magicObjProp2', 'magicObjMethod', + //'magicStaticMethod', 'publicObjProp', 'protectedObjProp', diff --git a/tests/Internal/JsonTest.php b/tests/Internal/JsonTest.php index a84e9ec1e4e..2096e5a1129 100644 --- a/tests/Internal/JsonTest.php +++ b/tests/Internal/JsonTest.php @@ -1,5 +1,7 @@ assertStringNotContainsString("ERROR", $output, "all issues baselined"); IssueBuffer::clear(); } @@ -137,7 +139,7 @@ public function testPrintSuccessMessageWorks(): void $project_analyzer->stdout_report_options = new ReportOptions; ob_start(); IssueBuffer::printSuccessMessage($project_analyzer); - $output = ob_get_clean(); + $output = (string) ob_get_clean(); $this->assertStringContainsString('No errors found!', $output); } diff --git a/tests/IssueSuppressionTest.php b/tests/IssueSuppressionTest.php index beeb66bfe80..3b703bb4260 100644 --- a/tests/IssueSuppressionTest.php +++ b/tests/IssueSuppressionTest.php @@ -1,5 +1,7 @@ expectExceptionMessage('UnusedPsalmSuppress'); $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); } public function testIssueSuppressedOnStatement(): void @@ -54,13 +56,13 @@ public function testIssueSuppressedOnStatement(): void $this->expectExceptionMessage('UnusedPsalmSuppress'); $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); } public function testUnusedSuppressAllOnFunction(): void @@ -70,7 +72,7 @@ public function testUnusedSuppressAllOnFunction(): void $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); } public function testUnusedSuppressAllOnStatement(): void @@ -87,12 +89,12 @@ public function testUnusedSuppressAllOnStatement(): void $this->expectExceptionMessage('UnusedPsalmSuppress'); $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', new Context()); } public function testMissingThrowsDocblockSuppressed(): void @@ -100,7 +102,7 @@ public function testMissingThrowsDocblockSuppressed(): void Config::getInstance()->check_for_throws_docblock = true; $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); } public function testMissingThrowsDocblockSuppressedWithoutThrow(): void @@ -127,7 +129,7 @@ public function testMissingThrowsDocblockSuppressedWithoutThrow(): void Config::getInstance()->check_for_throws_docblock = true; $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); } public function testMissingThrowsDocblockSuppressedDuplicate(): void @@ -147,7 +149,7 @@ public function testMissingThrowsDocblockSuppressedDuplicate(): void Config::getInstance()->check_for_throws_docblock = true; $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); } public function testUncaughtThrowInGlobalScopeSuppressed(): void @@ -166,7 +168,7 @@ public function testUncaughtThrowInGlobalScopeSuppressed(): void Config::getInstance()->check_for_throws_in_global_scope = true; $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); } public function testUncaughtThrowInGlobalScopeSuppressedWithoutThrow(): void @@ -195,7 +197,7 @@ public function testUncaughtThrowInGlobalScopeSuppressedWithoutThrow(): void Config::getInstance()->check_for_throws_in_global_scope = true; $this->addFile( - getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', + (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', 'analyzeFile(getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); + $this->analyzeFile((string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php', $context); } public function testPossiblyUnusedPropertySuppressedOnClass(): void { $this->project_analyzer->getCodebase()->find_unused_code = "always"; - $file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; + $file_path = (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; $this->addFile( $file_path, 'project_analyzer->getCodebase()->find_unused_code = "always"; - $file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; + $file_path = (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; $this->addFile( $file_path, 'addFile('somefile.php', $code); $this->analyzeFile('somefile.php', new Context()); @@ -88,7 +90,7 @@ function fooFoo(int $a): string { function fooFoo(int $a): int { return $b + 1; }', - 'error_count' => 5, + 'error_count' => 4, 'message' => 'Cannot find referenced variable $b', 'line' => 3, 'error' => '$b', @@ -98,7 +100,7 @@ function fooFoo(int $a): int { function fooFoo(Badger\Bodger $a): Badger\Bodger { return $a; }', - 'error_count' => 3, + 'error_count' => 2, 'message' => 'Class, interface or enum named Badger\\Bodger does not exist', 'line' => 2, 'error' => 'Badger\\Bodger', diff --git a/tests/LanguageServer/CompletionTest.php b/tests/LanguageServer/CompletionTest.php index 656951253d5..0d99a08fdf0 100644 --- a/tests/LanguageServer/CompletionTest.php +++ b/tests/LanguageServer/CompletionTest.php @@ -1,5 +1,7 @@ setTimeout(5000); $clientConfiguration = new ClientConfiguration(); @@ -87,14 +88,14 @@ public function testSnippetSupportDisabled(): void $this->codebase, $clientConfiguration, new Progress, - new PathMapper(getcwd(), getcwd()), + new PathMapper((string) getcwd(), (string) getcwd()), ); $write->on('message', function (Message $message) use ($deferred, $server): void { /** @psalm-suppress NullPropertyFetch,PossiblyNullPropertyFetch,UndefinedPropertyFetch */ if ($message->body->method === 'telemetry/event' && ($message->body->params->message ?? null) === 'initialized') { $this->assertFalse($server->clientCapabilities->textDocument->completion->completionItem->snippetSupport); - $deferred->resolve(null); + $deferred->complete(null); return; } @@ -104,12 +105,12 @@ public function testSnippetSupportDisabled(): void && ($message->body->params->value->message ?? null) === 'initialized' ) { $this->assertFalse($server->clientCapabilities->textDocument->completion->completionItem->snippetSupport); - $deferred->resolve(null); + $deferred->complete(null); return; } }); - wait($deferred->promise()); + $deferred->getFuture()->await(); } /** diff --git a/tests/LanguageServer/FileMapTest.php b/tests/LanguageServer/FileMapTest.php index e17b19be5d1..b89aa90a23b 100644 --- a/tests/LanguageServer/FileMapTest.php +++ b/tests/LanguageServer/FileMapTest.php @@ -1,5 +1,7 @@ emit('message', [Message::parse((string)$msg)]); }); - - // Create a new promisor - $deferred = new Deferred; - - $deferred->resolve(null); - - return $deferred->promise(); } } diff --git a/tests/LanguageServer/PathMapperTest.php b/tests/LanguageServer/PathMapperTest.php index 2e64b356399..eeed17d924f 100644 --- a/tests/LanguageServer/PathMapperTest.php +++ b/tests/LanguageServer/PathMapperTest.php @@ -1,5 +1,7 @@ configureClientRoot($client_root_provided_later); @@ -53,7 +55,7 @@ public function testMapsServerToClient( ?string $client_root_preconfigured, string $client_root_provided_later, string $client_path, - string $server_path + string $server_path, ): void { $mapper = new PathMapper($server_root, $client_root_preconfigured); $mapper->configureClientRoot($client_root_provided_later); diff --git a/tests/LanguageServer/SymbolLookupTest.php b/tests/LanguageServer/SymbolLookupTest.php index d235bc33442..a0a36cb5dd2 100644 --- a/tests/LanguageServer/SymbolLookupTest.php +++ b/tests/LanguageServer/SymbolLookupTest.php @@ -1,5 +1,7 @@ codebase->config; $config->throw_exception = false; diff --git a/tests/ListTest.php b/tests/ListTest.php index 5dce65e5491..c36c4bac7df 100644 --- a/tests/ListTest.php +++ b/tests/ListTest.php @@ -1,5 +1,7 @@ $arr */ function cartesianProduct(array $arr) : void { - for ($i = 20; $arr[$i] === 5 && $i > 0; $i--) {} + for ($i = 20; $i > 0 && $arr[$i] === 5 ; $i--) {} }', ], 'noCrashOnLongThing' => [ diff --git a/tests/Loop/ForeachTest.php b/tests/Loop/ForeachTest.php index 0777815cb5e..3ffab0dd13c 100644 --- a/tests/Loop/ForeachTest.php +++ b/tests/Loop/ForeachTest.php @@ -1,5 +1,7 @@ [ 'code' => ' [ + 'code' => ' [], + 'ignored_issues' => ['ParamNameMismatch'], + ], ]; } @@ -1295,6 +1312,21 @@ class B extends A {} $b->foo();', 'error_message' => 'UndefinedMagicMethod', ], + 'inheritSealedMethodsWithoutPrefix' => [ + 'code' => 'foo();', + 'error_message' => 'UndefinedMagicMethod', + ], 'inheritSealedMethodsWithStatic' => [ 'code' => ' 'UndefinedMagicMethod', ], @@ -1340,6 +1371,79 @@ public function baz(): Foo }', 'error_message' => 'UndefinedVariable', ], + 'MagicMethodReturnTypesCheckedForClasses' => [ + 'code' => ' 'ImplementedReturnTypeMismatch', + ], + 'MagicMethodParamTypesCheckedForClasses' => [ + 'code' => ' 'ImplementedParamTypeMismatch', + ], + 'MagicMethodReturnTypesCheckedForInterfaces' => [ + 'code' => ' 'ImplementedReturnTypeMismatch', + ], + 'MagicMethodParamTypesCheckedForInterfaces' => [ + 'code' => ' 'ImplementedParamTypeMismatch', + ], + 'SKIPPED-MagicMethodMadeConcreteChecksParams' => [ + 'code' => ' 'ImplementedParamTypeMismatch', + ], 'staticInvocationWithMagicMethodFoo' => [ 'code' => 'bar(function_does_not_exist(123)); PHP, @@ -1422,7 +1526,7 @@ class B extends A {} /** @method static int bar() */ class A {} class B extends A {} - + /** @psalm-suppress UndefinedMethod */ $a = B::bar(function_does_not_exist(123)); PHP, diff --git a/tests/MagicPropertyTest.php b/tests/MagicPropertyTest.php index bc1e89faffe..5b0f7a0734b 100644 --- a/tests/MagicPropertyTest.php +++ b/tests/MagicPropertyTest.php @@ -1,5 +1,7 @@ [], - 'ignored_issues' => ['MixedReturnStatement', 'MixedInferredReturnType'], + 'ignored_issues' => ['MixedReturnStatement'], ], 'overrideInheritedProperty' => [ 'code' => ' 'InvalidDocblock', ], + 'sealedWithNoProperties' => [ + 'code' => 'errors;', + 'error_message' => 'UndefinedMagicPropertyFetch', + ], + 'sealedWithNoPropertiesNoPrefix' => [ + 'code' => 'errors;', + 'error_message' => 'UndefinedMagicPropertyFetch', + ], ]; } diff --git a/tests/MatchTest.php b/tests/MatchTest.php index 64f2c4f5e0d..43c1e0c1492 100644 --- a/tests/MatchTest.php +++ b/tests/MatchTest.php @@ -1,5 +1,7 @@ [], - 'ignored_issues' => ['MixedReturnStatement', 'MixedInferredReturnType'], + 'ignored_issues' => ['MixedReturnStatement'], 'php_version' => '8.0', ], 'nullsafeShortCircuit' => [ @@ -1363,7 +1365,7 @@ public function returns_nullable_class() { } }', 'error_message' => 'LessSpecificReturnStatement', - 'ignored_issues' => ['MixedInferredReturnType', 'MixedReturnStatement', 'MixedMethodCall'], + 'ignored_issues' => ['MixedReturnStatement', 'MixedMethodCall'], ], 'undefinedVariableStaticCall' => [ 'code' => ' 'B', ], ], + 'returnIgnoresInlineComments' => [ + 'code' => ' [ 'code' => ' [], + 'ignored_issues' => [], + 'php_version' => '8.0', ], 'doesNotRequireInterfaceDestructorsToHaveReturnType' => [ 'code' => ' 'MethodSignatureMismatch', ], + 'methodAnnotationReturnMismatch' => [ + 'code' => ' 'MismatchingDocblockReturnType', + ], + 'methodAnnotationParamMismatch' => [ + 'code' => ' 'MismatchingDocblockParamType', + ], ]; } } diff --git a/tests/MixinAnnotationTest.php b/tests/MixinAnnotationTest.php index 3c0c11b26bb..0d45f5978a4 100644 --- a/tests/MixinAnnotationTest.php +++ b/tests/MixinAnnotationTest.php @@ -1,5 +1,7 @@ 'list', ], ], + 'mixinInheritMagicMethods' => [ + 'code' => 'active();', + 'assertions' => [ + '$c' => 'B', + ], + ], ]; } diff --git a/tests/NamespaceTest.php b/tests/NamespaceTest.php index 60d989a22bd..3e1736214f1 100644 --- a/tests/NamespaceTest.php +++ b/tests/NamespaceTest.php @@ -1,5 +1,7 @@ project_analyzer->check('tests/fixtures/DummyProject'); - $output = ob_get_clean(); + $output = (string) ob_get_clean(); $this->assertStringContainsString('Target PHP version: 8.1 (set by tests)', $output); $this->assertStringContainsString('Scanning files...', $output); @@ -226,7 +232,7 @@ public function testCheckAfterFileChange(): void ), ); - $bat_file_path = getcwd() + $bat_file_path = (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'fixtures' . DIRECTORY_SEPARATOR . 'DummyProject' @@ -281,7 +287,7 @@ public function testCheckDir(): void ob_start(); $this->project_analyzer->checkDir('tests/fixtures/DummyProject'); - $output = ob_get_clean(); + $output = (string) ob_get_clean(); $this->assertStringContainsString('Target PHP version: 8.1 (set by tests)', $output); $this->assertStringContainsString('Scanning files...', $output); @@ -319,10 +325,10 @@ public function testCheckPaths(): void // checkPaths expects absolute paths, // otherwise it's unable to match them against configured folders $this->project_analyzer->checkPaths([ - realpath(getcwd() . '/tests/fixtures/DummyProject/Bar.php'), - realpath(getcwd() . '/tests/fixtures/DummyProject/SomeTrait.php'), + (string) realpath((string) getcwd() . '/tests/fixtures/DummyProject/Bar.php'), + (string) realpath((string) getcwd() . '/tests/fixtures/DummyProject/SomeTrait.php'), ]); - $output = ob_get_clean(); + $output = (string) ob_get_clean(); $this->assertStringContainsString('Target PHP version: 8.1 (set by tests)', $output); $this->assertStringContainsString('Scanning files...', $output); @@ -360,10 +366,10 @@ public function testCheckFile(): void // checkPaths expects absolute paths, // otherwise it's unable to match them against configured folders $this->project_analyzer->checkPaths([ - realpath(getcwd() . '/tests/fixtures/DummyProject/Bar.php'), - realpath(getcwd() . '/tests/fixtures/DummyProject/SomeTrait.php'), + (string) realpath((string) getcwd() . '/tests/fixtures/DummyProject/Bar.php'), + (string) realpath((string) getcwd() . '/tests/fixtures/DummyProject/SomeTrait.php'), ]); - $output = ob_get_clean(); + $output = (string) ob_get_clean(); $this->assertStringContainsString('Target PHP version: 8.1 (set by tests)', $output); $this->assertStringContainsString('Scanning files...', $output); diff --git a/tests/PropertiesOfTest.php b/tests/PropertiesOfTest.php index 73562916830..99a14e678d6 100644 --- a/tests/PropertiesOfTest.php +++ b/tests/PropertiesOfTest.php @@ -1,5 +1,7 @@ $1", $output); + $output = (string) preg_replace("#({$needles})#im", "$1", $output); return $output; }', diff --git a/tests/PureCallableTest.php b/tests/PureCallableTest.php index 79817fb3a0e..6360ee55412 100644 --- a/tests/PureCallableTest.php +++ b/tests/PureCallableTest.php @@ -1,5 +1,7 @@ 'https://psalm.dev/024', 'severity' => 'error', 'line_from' => 3, 'line_to' => 3, @@ -726,13 +729,13 @@ public function testJsonReport(): void 'snippet_to' => 83, 'column_from' => 10, 'column_to' => 26, - 'error_level' => -1, 'shortcode' => 24, - 'link' => 'https://psalm.dev/024', + 'error_level' => -1, 'taint_trace' => null, 'other_references' => null, ], [ + 'link' => 'https://psalm.dev/138', 'severity' => 'error', 'line_from' => 3, 'line_to' => 3, @@ -748,35 +751,13 @@ public function testJsonReport(): void 'snippet_to' => 83, 'column_from' => 10, 'column_to' => 26, - 'error_level' => 1, 'shortcode' => 138, - 'link' => 'https://psalm.dev/138', - 'taint_trace' => null, - 'other_references' => null, - ], - [ - 'severity' => 'error', - 'line_from' => 2, - 'line_to' => 2, - 'type' => 'MixedInferredReturnType', - 'message' => 'Could not verify return type \'null|string\' for psalmCanVerify', - 'file_name' => 'somefile.php', - 'file_path' => 'somefile.php', - 'snippet' => 'function psalmCanVerify(int $your_code): ?string {', - 'selected_text' => '?string', - 'from' => 47, - 'to' => 54, - 'snippet_from' => 6, - 'snippet_to' => 56, - 'column_from' => 42, - 'column_to' => 49, 'error_level' => 1, - 'shortcode' => 47, - 'link' => 'https://psalm.dev/047', 'taint_trace' => null, 'other_references' => null, ], [ + 'link' => 'https://psalm.dev/020', 'severity' => 'error', 'line_from' => 8, 'line_to' => 8, @@ -792,13 +773,13 @@ public function testJsonReport(): void 'snippet_to' => 172, 'column_from' => 6, 'column_to' => 15, - 'error_level' => -1, 'shortcode' => 20, - 'link' => 'https://psalm.dev/020', + 'error_level' => -1, 'taint_trace' => null, 'other_references' => null, ], [ + 'link' => 'https://psalm.dev/126', 'severity' => 'info', 'line_from' => 17, 'line_to' => 17, @@ -814,9 +795,8 @@ public function testJsonReport(): void 'snippet_to' => 277, 'column_from' => 6, 'column_to' => 8, - 'error_level' => 3, 'shortcode' => 126, - 'link' => 'https://psalm.dev/126', + 'error_level' => 3, 'taint_trace' => null, 'other_references' => null, ], @@ -853,7 +833,7 @@ public function testFilteredJsonReportIsStillArray(): void ]; $report_options = ProjectAnalyzer::getFileReportOptions([__DIR__ . '/test-report.json'])[0]; - $fixable_issue_counts = ['MixedInferredReturnType' => 1]; + $fixable_issue_counts = []; $report = new JsonReport( $issues_data, @@ -901,22 +881,6 @@ public function testSonarqubeReport(): void 'type' => 'CODE_SMELL', 'severity' => 'CRITICAL', ], - [ - 'engineId' => 'Psalm', - 'ruleId' => 'MixedInferredReturnType', - 'primaryLocation' => [ - 'message' => 'Could not verify return type \'null|string\' for psalmCanVerify', - 'filePath' => 'somefile.php', - 'textRange' => [ - 'startLine' => 2, - 'endLine' => 2, - 'startColumn' => 41, - 'endColumn' => 48, - ], - ], - 'type' => 'CODE_SMELL', - 'severity' => 'CRITICAL', - ], [ 'engineId' => 'Psalm', 'ruleId' => 'UndefinedConstant', @@ -971,7 +935,6 @@ public function testEmacsReport(): void <<<'EOF' somefile.php:3:10:error - UndefinedVariable: Cannot find referenced variable $as_you_____type (see https://psalm.dev/024) somefile.php:3:10:error - MixedReturnStatement: Could not infer a return type (see https://psalm.dev/138) - somefile.php:2:42:error - MixedInferredReturnType: Could not verify return type 'null|string' for psalmCanVerify (see https://psalm.dev/047) somefile.php:8:6:error - UndefinedConstant: Const CHANGE_ME is not defined (see https://psalm.dev/020) somefile.php:17:6:warning - PossiblyUndefinedGlobalVariable: Possibly undefined global variable $a, first seen on line 11 (see https://psalm.dev/126) @@ -990,7 +953,6 @@ public function testPylintReport(): void <<<'EOF' somefile.php:3: [E0001] UndefinedVariable: Cannot find referenced variable $as_you_____type (column 10) somefile.php:3: [E0001] MixedReturnStatement: Could not infer a return type (column 10) - somefile.php:2: [E0001] MixedInferredReturnType: Could not verify return type 'null|string' for psalmCanVerify (column 42) somefile.php:8: [E0001] UndefinedConstant: Const CHANGE_ME is not defined (column 6) somefile.php:17: [W0001] PossiblyUndefinedGlobalVariable: Possibly undefined global variable $a, first seen on line 11 (column 6) @@ -1014,9 +976,6 @@ public function testConsoleReport(): void ERROR: MixedReturnStatement - somefile.php:3:10 - Could not infer a return type (see https://psalm.dev/138) return $as_you_____type; - ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type 'null|string' for psalmCanVerify (see https://psalm.dev/047) - function psalmCanVerify(int $your_code): ?string { - ERROR: UndefinedConstant - somefile.php:8:6 - Const CHANGE_ME is not defined (see https://psalm.dev/020) echo CHANGE_ME; @@ -1045,9 +1004,6 @@ public function testConsoleReportNoInfo(): void ERROR: MixedReturnStatement - somefile.php:3:10 - Could not infer a return type (see https://psalm.dev/138) return $as_you_____type; - ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type 'null|string' for psalmCanVerify (see https://psalm.dev/047) - function psalmCanVerify(int $your_code): ?string { - ERROR: UndefinedConstant - somefile.php:8:6 - Const CHANGE_ME is not defined (see https://psalm.dev/020) echo CHANGE_ME; @@ -1073,9 +1029,6 @@ public function testConsoleReportNoSnippet(): void ERROR: MixedReturnStatement - somefile.php:3:10 - Could not infer a return type (see https://psalm.dev/138) - ERROR: MixedInferredReturnType - somefile.php:2:42 - Could not verify return type 'null|string' for psalmCanVerify (see https://psalm.dev/047) - - ERROR: UndefinedConstant - somefile.php:8:6 - Const CHANGE_ME is not defined (see https://psalm.dev/020) @@ -1134,15 +1087,14 @@ public function testCompactReport(): void <<<'EOF' FILE: somefile.php - +----------+------+---------------------------------+---------------------------------------------------------------+ - | SEVERITY | LINE | ISSUE | DESCRIPTION | - +----------+------+---------------------------------+---------------------------------------------------------------+ - | ERROR | 3 | UndefinedVariable | Cannot find referenced variable $as_you_____type | - | ERROR | 3 | MixedReturnStatement | Could not infer a return type | - | ERROR | 2 | MixedInferredReturnType | Could not verify return type 'null|string' for psalmCanVerify | - | ERROR | 8 | UndefinedConstant | Const CHANGE_ME is not defined | - | INFO | 17 | PossiblyUndefinedGlobalVariable | Possibly undefined global variable $a, first seen on line 11 | - +----------+------+---------------------------------+---------------------------------------------------------------+ + +----------+------+---------------------------------+--------------------------------------------------------------+ + | SEVERITY | LINE | ISSUE | DESCRIPTION | + +----------+------+---------------------------------+--------------------------------------------------------------+ + | ERROR | 3 | UndefinedVariable | Cannot find referenced variable $as_you_____type | + | ERROR | 3 | MixedReturnStatement | Could not infer a return type | + | ERROR | 8 | UndefinedConstant | Const CHANGE_ME is not defined | + | INFO | 17 | PossiblyUndefinedGlobalVariable | Possibly undefined global variable $a, first seen on line 11 | + +----------+------+---------------------------------+--------------------------------------------------------------+ EOF, $this->toUnixLineEndings(IssueBuffer::getOutput(IssueBuffer::getIssuesData(), $compact_report_options)), @@ -1165,9 +1117,6 @@ public function testCheckstyleReport(): void - - - @@ -1198,8 +1147,8 @@ public function testJunitReport(): void $this->assertSame( <<<'EOF' - - + + message: Cannot find referenced variable $as_you_____type type: UndefinedVariable @@ -1218,16 +1167,6 @@ public function testJunitReport(): void line: 3 column_from: 10 column_to: 26 - - - - message: Could not verify return type 'null|string' for psalmCanVerify - type: MixedInferredReturnType - snippet: function psalmCanVerify(int $your_code): ?string { - selected_text: ?string - line: 2 - column_from: 42 - column_to: 49 @@ -1282,7 +1221,6 @@ public function testGithubActionsOutput(): void $expected_output = <<<'EOF' ::error file=somefile.php,line=3,col=10,title=UndefinedVariable::somefile.php:3:10: UndefinedVariable: Cannot find referenced variable $as_you_____type (see https://psalm.dev/024) ::error file=somefile.php,line=3,col=10,title=MixedReturnStatement::somefile.php:3:10: MixedReturnStatement: Could not infer a return type (see https://psalm.dev/138) - ::error file=somefile.php,line=2,col=42,title=MixedInferredReturnType::somefile.php:2:42: MixedInferredReturnType: Could not verify return type 'null|string' for psalmCanVerify (see https://psalm.dev/047) ::error file=somefile.php,line=8,col=6,title=UndefinedConstant::somefile.php:8:6: UndefinedConstant: Const CHANGE_ME is not defined (see https://psalm.dev/020) ::warning file=somefile.php,line=17,col=6,title=PossiblyUndefinedGlobalVariable::somefile.php:17:6: PossiblyUndefinedGlobalVariable: Possibly undefined global variable $a, first seen on line 11 (see https://psalm.dev/126) @@ -1300,7 +1238,6 @@ public function testCountOutput(): void $report_options = new ReportOptions(); $report_options->format = Report::TYPE_COUNT; $expected_output = <<<'EOF' - MixedInferredReturnType: 1 MixedReturnStatement: 1 PossiblyUndefinedGlobalVariable: 1 UndefinedConstant: 1 @@ -1368,6 +1305,6 @@ public function testEmptyReportIfNotError(): void */ private function toUnixLineEndings(string $output): string { - return preg_replace('~\r\n?~', "\n", $output); + return (string) preg_replace('~\r\n?~', "\n", $output); } } diff --git a/tests/ReturnTypeProvider/ArrayColumnTest.php b/tests/ReturnTypeProvider/ArrayColumnTest.php index ea6a8d604f1..143557369d5 100644 --- a/tests/ReturnTypeProvider/ArrayColumnTest.php +++ b/tests/ReturnTypeProvider/ArrayColumnTest.php @@ -1,5 +1,7 @@ [ + 'code' => <<<'PHP' + [ + 'code' => <<<'PHP' + foo; + } + } + PHP, + ], + 'returnByReferenceVariableInFunction' => [ + 'code' => <<<'PHP' + [ 'code' => ' 'MissingReturnType', ], - 'mixedInferredReturnType' => [ - 'code' => ' 'MixedInferredReturnType', - ], 'mixedInferredReturnStatement' => [ 'code' => ' 'MixedReturnStatement', ], - 'invalidReturnTypeClass' => [ - 'code' => ' 'UndefinedClass', - 'ignored_issues' => ['MixedInferredReturnType'], - ], 'invalidClassOnCall' => [ 'code' => 'bar();', 'error_message' => 'UndefinedClass', - 'ignored_issues' => ['MixedInferredReturnType', 'MixedReturnStatement'], + 'ignored_issues' => ['MixedReturnStatement'], ], 'returnArrayOfNullableInvalid' => [ 'code' => ' [], 'php_version' => '8.0', ], + 'returnByReferenceNonVariableInStaticMethod' => [ + 'code' => <<<'PHP' + 'NonVariableReferenceReturn', + ], + 'returnByReferenceNonVariableInInstanceMethod' => [ + 'code' => <<<'PHP' + 'NonVariableReferenceReturn', + ], + 'returnByReferenceNonVariableInFunction' => [ + 'code' => <<<'PHP' + 'NonVariableReferenceReturn', + ], 'implicitReturnFromFunctionWithNeverReturnType' => [ 'code' => <<<'PHP' addFile( $file_path, @@ -154,6 +157,7 @@ public function testLoadStubFileWithRelativePath(): void $path = $this->getOperatingSystemStyledPath('tests/fixtures/stubs/systemclass.phpstub'); $stub_files = $this->project_analyzer->getConfig()->getStubFiles(); + assert(!empty($stub_files)); $this->assertStringContainsString($path, reset($stub_files)); } @@ -176,6 +180,7 @@ public function testLoadStubFileWithAbsolutePath(): void $path = $this->getOperatingSystemStyledPath('tests/fixtures/stubs/systemclass.phpstub'); $stub_files = $this->project_analyzer->getConfig()->getStubFiles(); + assert(!empty($stub_files)); $this->assertStringContainsString($path, reset($stub_files)); } @@ -199,7 +204,7 @@ public function testStubFileConstant(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -218,7 +223,7 @@ public function testStubFileConstant(): void public function testStubFileParentClass(): void { $this->expectException(CodeException::class); - $this->expectExceptionMessage('MethodSignatureMismatch'); + $this->expectExceptionMessage('ImplementedParamTypeMismatch'); $this->project_analyzer = $this->getProjectAnalyzerWithConfig( TestConfig::loadFromXML( dirname(__DIR__), @@ -237,7 +242,7 @@ public function testStubFileParentClass(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -279,7 +284,7 @@ public function testStubFileCircularReference(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -311,7 +316,7 @@ public function testPhpStormMetaParsingFile(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -485,7 +490,7 @@ public function testNamespacedStubClass(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -522,7 +527,7 @@ public function testStubRegularFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -553,7 +558,7 @@ public function testStubVariadicFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -586,7 +591,7 @@ public function testStubVariadicFunctionWrongArgType(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -615,7 +620,7 @@ public function testUserVariadicWithFalseVariadic(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -650,7 +655,7 @@ public function testPolyfilledFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -682,7 +687,7 @@ public function testConditionalConstantDefined(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -717,7 +722,7 @@ public function testStubbedConstantVarCommentType(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -756,7 +761,7 @@ public function testClassAlias(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -817,7 +822,7 @@ public function testStubFunctionWithFunctionExists(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -849,7 +854,7 @@ public function testNamespacedStubFunctionWithFunctionExists(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -880,7 +885,7 @@ public function testNoStubFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -911,7 +916,7 @@ public function testNamespacedStubFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -942,7 +947,7 @@ public function testConditionalNamespacedStubFunction(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -973,7 +978,7 @@ public function testConditionallyExtendingInterface(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1020,7 +1025,7 @@ public function testStubFileWithExistingClassDefinition(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1070,7 +1075,7 @@ public function testVersionDependentStubs(string $php_version, string $code): vo ); $this->project_analyzer->setPhpVersion($php_version, 'tests'); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile($file_path, $code); @@ -1097,7 +1102,7 @@ public function testStubFileWithPartialClassDefinitionWithMoreMethods(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1145,7 +1150,7 @@ public function testExtendOnlyStubbedClass(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1180,7 +1185,7 @@ public function testStubFileWithExtendedStubbedClass(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1215,7 +1220,7 @@ public function testStubFileWithTemplatedClassDefinitionAndMagicMethodOverride() ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1266,7 +1271,7 @@ public function testInheritedMethodUsedInStub(): void $this->project_analyzer->getCodebase()->reportUnusedCode(); - $vendor_file_path = getcwd() . '/vendor/vendor_class.php'; + $vendor_file_path = (string) getcwd() . '/vendor/vendor_class.php'; $this->addFile( $vendor_file_path, @@ -1282,7 +1287,7 @@ public static function vendorFunction(VendorClass $v) : void { }', ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1316,7 +1321,7 @@ public function testStubOverridingMissingClass(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1347,7 +1352,7 @@ public function testStubOverridingMissingMethod(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1379,7 +1384,7 @@ public function testStubReplacingInterfaceDocblock(): void ); $this->addFile( - getcwd() . '/vendor/doctrine/import.php', + (string) getcwd() . '/vendor/doctrine/import.php', 'addFile( $file_path, @@ -1427,4 +1432,48 @@ function em(EntityManager $em) : void { $this->analyzeFile($file_path, new Context()); } + + /** + * This covers the following case encountered by mmcev106: + * - A function was defined without a docblock + * - The autoloader defined a global containing the path to that file + * - The code being scanned required the path specified by the autoloader defined global + * - A docblock was added via a stub that marked the function as a taint source + * - The stub docblock was incorrectly ignored, causing the the taint source to be ignored + */ + public function testAutoloadDefinedRequirePath(): void + { + $this->project_analyzer = $this->getProjectAnalyzerWithConfig( + TestConfig::loadFromXML( + dirname(__DIR__), + ' + + + + + + + + + ', + ), + ); + + $this->project_analyzer->trackTaintedInputs(); + + $file_path = (string) getcwd() . DIRECTORY_SEPARATOR . 'src/somefile.php'; + + $this->addFile( + $file_path, + 'expectExceptionMessage('TaintedHtml - src/somefile.php'); + $this->analyzeFile($file_path, new Context()); + } } diff --git a/tests/SuperGlobalsTest.php b/tests/SuperGlobalsTest.php index e0e33418e95..42e6212d628 100644 --- a/tests/SuperGlobalsTest.php +++ b/tests/SuperGlobalsTest.php @@ -1,5 +1,7 @@ query("$a$b$c$d");', ], + 'querySimpleXMLElement' => [ + 'code' => 'xpath($expression); + }', + ], + 'escapeSeconds' => [ + 'code' => 'invoke();', 'error_message' => 'TaintedCallable', ], + 'querySimpleXMLElement' => [ + 'code' => 'xpath($expression); + }', + 'error_message' => 'TaintedXpath', + ], + 'queryDOMXPath' => [ + 'code' => 'query($expression); + }', + 'error_message' => 'TaintedXpath', + ], + 'evaluateDOMXPath' => [ + 'code' => 'evaluate($expression); + }', + 'error_message' => 'TaintedXpath', + ], + 'taintedSleep' => [ + 'code' => ' 'TaintedSleep', + ], + 'taintedUsleep' => [ + 'code' => ' 'TaintedSleep', + ], + 'taintedTimeNanosleepSeconds' => [ + 'code' => ' 'TaintedSleep', + ], + 'taintedTimeNanosleepNanoseconds' => [ + 'code' => ' 'TaintedSleep', + ], + 'taintedTimeSleepUntil' => [ + 'code' => ' 'TaintedSleep', + ], + 'taintedExtract' => [ + 'code' => ' 'TaintedExtract', + ], + 'extractPost' => [ + 'code' => ' 'TaintedExtract', + ], 'taintedExecuteQueryFunction' => [ 'code' => ' 'string', ], ], + 'InheritFuncNumArgs' => [ + 'code' => ' [ 'code' => ' [], 'php_version' => '7.2', ], + 'ineritedreturnTypeBasedOnPhpVersionId' => [ + 'code' => ' ? string : int) + */ + function getSomething() + { + return mt_rand(1, 10) > 5 ? "a value" : 42; + } + + /** + * @psalm-return (PHP_VERSION_ID is int<70100, max> ? string : int) + */ + function getSomethingElse() + { + return mt_rand(1, 10) > 5 ? "a value" : 42; + } + } + + class B extends A {} + + $class = new B(); + $something = $class->getSomething(); + $somethingElse = $class->getSomethingElse(); + ', + 'assertions' => [ + '$something' => 'int', + '$somethingElse' => 'string', + ], + 'ignored_issues' => [], + 'php_version' => '7.2', + ], 'ineritedConditionalTemplatedReturnType' => [ 'code' => ' $className * @psalm-return RequestedType&MockObject - * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedReturnStatement */ function mockHelper(string $className) @@ -442,7 +443,6 @@ public function checkExpectations() : void * @psalm-template RequestedType * @psalm-param class-string $className * @psalm-return RequestedType&MockObject - * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedReturnStatement */ function mockHelper(string $className) @@ -480,7 +480,6 @@ public function checkExpectations() : void * @psalm-template RequestedType * @psalm-param class-string $className * @psalm-return MockObject&RequestedType - * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedReturnStatement */ function mockHelper(string $className) diff --git a/tests/Template/FunctionTemplateAssertTest.php b/tests/Template/FunctionTemplateAssertTest.php index 94eed865888..5ecc63379c7 100644 --- a/tests/Template/FunctionTemplateAssertTest.php +++ b/tests/Template/FunctionTemplateAssertTest.php @@ -1,5 +1,7 @@ [ + 'code' => ' + */ + use MyTrait; + } + + class Bar { + /** + * @template-use MyTrait + */ + use MyTrait; + }', + ], 'allowTraitExtendAndImplementWithExplicitParamType' => [ 'code' => 'use_docblock_types = true; $this->level = 1; $this->cache_directory = null; + $this->ignore_internal_falsable_issues = true; + $this->ignore_internal_nullable_issues = true; - $this->base_dir = getcwd(); + $this->base_dir = (string) getcwd(); if (!self::$cached_project_files) { self::$cached_project_files = ProjectFileFilter::loadFromXMLElement( @@ -55,9 +59,7 @@ protected function getContents(): string '; } - /** - * @return false - */ + /** @return false */ public function getComposerFilePathForClassLike(string $fq_classlike_name): bool { return false; diff --git a/tests/TestEnvironmentTest.php b/tests/TestEnvironmentTest.php index 5a123791289..7b6a1b587b1 100644 --- a/tests/TestEnvironmentTest.php +++ b/tests/TestEnvironmentTest.php @@ -1,5 +1,7 @@ [], 'php_version' => '8.1', ], + 'duplicateTraitProperty' => [ + 'code' => ' 'DuplicateProperty', + ], ]; } } diff --git a/tests/Traits/InvalidCodeAnalysisTestTrait.php b/tests/Traits/InvalidCodeAnalysisTestTrait.php index bcc7f7be641..f69fa7e0abd 100644 --- a/tests/Traits/InvalidCodeAnalysisTestTrait.php +++ b/tests/Traits/InvalidCodeAnalysisTestTrait.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'PHP80-') !== false) { @@ -87,7 +89,7 @@ public function testInvalidCode( $file_path = self::$src_dir_path . 'somefile.php'; - // $error_message = preg_replace('/ src[\/\\\\]somefile\.php/', ' src/somefile.php', $error_message); + // $error_message = (string) preg_replace('/ src[\/\\\\]somefile\.php/', ' src/somefile.php', $error_message); $this->expectException(CodeException::class); diff --git a/tests/Traits/InvalidCodeAnalysisWithIssuesTestTrait.php b/tests/Traits/InvalidCodeAnalysisWithIssuesTestTrait.php index cfe4dba87b0..4965cf25460 100644 --- a/tests/Traits/InvalidCodeAnalysisWithIssuesTestTrait.php +++ b/tests/Traits/InvalidCodeAnalysisWithIssuesTestTrait.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'PHP80-') !== false) { diff --git a/tests/Traits/ValidCodeAnalysisTestTrait.php b/tests/Traits/ValidCodeAnalysisTestTrait.php index 5be98d38931..ca755ecf539 100644 --- a/tests/Traits/ValidCodeAnalysisTestTrait.php +++ b/tests/Traits/ValidCodeAnalysisTestTrait.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'PHP80-') !== false) { diff --git a/tests/TryCatchTest.php b/tests/TryCatchTest.php index c83e2b0911e..87a714e2359 100644 --- a/tests/TryCatchTest.php +++ b/tests/TryCatchTest.php @@ -1,5 +1,7 @@ [ + 'code' => ' $v + */ + function a(array $v): void {}', + ], 'typeAliasBeforeClass' => [ 'code' => ' 'array{phone: string}', ], ], + 'multilineTypeWithExtraSpace' => [ + 'code' => ' [ 'code' => 'assertSame( - 'callable-array{0: class-string, 1: string}', + 'callable-array{class-string, string}', (string)Type::parseString('callable-array{0: class-string, 1: string}'), ); } @@ -938,6 +940,14 @@ public function testClassStringMap(): void ); } + public function testClassStringMapOf(): void + { + $this->assertSame( + 'class-string-map', + Type::parseString('class-string-map')->getId(false), + ); + } + public function testVeryLargeType(): void { $very_large_type = 'array{a: Closure():(array|null), b?: Closure():array, c?: Closure():array, d?: Closure():array, e?: Closure():(array{f: null|string, g: null|string, h: null|string, i: string, j: mixed, k: mixed, l: mixed, m: mixed, n: bool, o?: array{0: string}}|null), p?: Closure():(array{f: null|string, g: null|string, h: null|string, i: string, j: mixed, k: mixed, l: mixed, m: mixed, n: bool, o?: array{0: string}}|null), q: string, r?: Closure():(array|null), s: array}|null'; diff --git a/tests/TypeReconciliation/ArrayKeyExistsTest.php b/tests/TypeReconciliation/ArrayKeyExistsTest.php index 4317379eb7b..283ac18d018 100644 --- a/tests/TypeReconciliation/ArrayKeyExistsTest.php +++ b/tests/TypeReconciliation/ArrayKeyExistsTest.php @@ -1,5 +1,7 @@ [], - 'ignored_issues' => ['MixedReturnStatement', 'MixedInferredReturnType'], + 'ignored_issues' => ['MixedReturnStatement'], ], 'assertSelfClassConstantOffsetsInFunction' => [ 'code' => ' [], - 'ignored_issues' => ['MixedReturnStatement', 'MixedInferredReturnType'], + 'ignored_issues' => ['MixedReturnStatement'], ], 'assertNamedClassConstantOffsetsInFunction' => [ 'code' => ' [], - 'ignored_issues' => ['MixedReturnStatement', 'MixedInferredReturnType'], + 'ignored_issues' => ['MixedReturnStatement'], ], 'possiblyUndefinedArrayAccessWithArrayKeyExists' => [ 'code' => ' [ 'code' => ' ' $arr - * @return non-empty-array + * @return array */ function foo(array $arr) : array { if (isset($arr["a"])) { @@ -1798,7 +1800,6 @@ class A { /** * @psalm-suppress MixedArrayAccess * @psalm-suppress MixedReturnStatement - * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedArrayAssignment */ public function foo() : stdClass { @@ -2189,7 +2190,6 @@ class C { /** * @psalm-suppress MixedReturnStatement - * @psalm-suppress MixedInferredReturnType * @psalm-suppress MixedArrayAccess */ public static function get(string $k1, string $k2) : ?string { @@ -2508,7 +2508,7 @@ function splitDocLine($return_block) continue; } - $remaining = trim(preg_replace(\'@^[ \t]*\* *@m\', \' \', substr($return_block, $i + 1))); + $remaining = trim((string) preg_replace(\'@^[ \t]*\* *@m\', \' \', substr($return_block, $i + 1))); if ($remaining) { /** @var array */ diff --git a/tests/TypeReconciliation/EmptyTest.php b/tests/TypeReconciliation/EmptyTest.php index bce863e0d34..8804692efab 100644 --- a/tests/TypeReconciliation/EmptyTest.php +++ b/tests/TypeReconciliation/EmptyTest.php @@ -1,5 +1,7 @@ project_analyzer->getCodebase()->queueClassLikeForScanning('Countable'); + $this->project_analyzer->getCodebase()->queueClassLikeForScanning(Countable::class); $this->project_analyzer->getCodebase()->scanFiles(); } @@ -157,7 +160,7 @@ public function providerTestReconcilation(): array 'nullableClassStringTruthy' => ['class-string', new Truthy(), 'class-string|null'], 'iterableToArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'iterable'], 'iterableToTraversable' => ['Traversable', new IsType(new TNamedObject('Traversable')), 'iterable'], - 'callableToCallableArray' => ['callable-array{0: class-string|object, 1: string}', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable'], + 'callableToCallableArray' => ['callable-array{class-string|object, non-empty-string}', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable'], 'SmallKeyedArrayAndCallable' => ['array{test: string}', new IsType(new TKeyedArray(['test' => Type::getString()])), 'callable'], 'BigKeyedArrayAndCallable' => ['array{foo: string, test: string, thing: string}', new IsType(new TKeyedArray(['foo' => Type::getString(), 'test' => Type::getString(), 'thing' => Type::getString()])), 'callable'], 'callableOrArrayToCallableArray' => ['array', new IsType(new TArray([Type::getArrayKey(), Type::getMixed()])), 'callable|array'], diff --git a/tests/TypeReconciliation/RedundantConditionTest.php b/tests/TypeReconciliation/RedundantConditionTest.php index 289bc20738f..19abb93e479 100644 --- a/tests/TypeReconciliation/RedundantConditionTest.php +++ b/tests/TypeReconciliation/RedundantConditionTest.php @@ -1,5 +1,7 @@ [ + 'code' => ' [ + 'code' => ' [ + 'code' => ' [ 'code' => ' [], - 'ignored_issues' => ['MixedInferredReturnType'], ], 'grandParentInstanceofConfusion' => [ 'code' => 'project_analyzer->getConfig()->throw_exception = false; - $file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; + $file_path = (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; $this->addFile( $file_path, @@ -137,7 +139,7 @@ function bar(): void{ public function testSeesUnusedClassReferencedByUnevaluatedCode(): void { $this->project_analyzer->getConfig()->throw_exception = false; - $file_path = getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; + $file_path = (string) getcwd() . DIRECTORY_SEPARATOR . 'tests' . DIRECTORY_SEPARATOR . 'somefile.php'; $this->addFile( $file_path, diff --git a/tests/UnusedVariableTest.php b/tests/UnusedVariableTest.php index 15bf8b182a3..345d04b5565 100644 --- a/tests/UnusedVariableTest.php +++ b/tests/UnusedVariableTest.php @@ -1,5 +1,7 @@ [ 'code' => 'addFile( $file_path, diff --git a/tests/autoload.php b/tests/autoload.php new file mode 100644 index 00000000000..449bca68efa --- /dev/null +++ b/tests/autoload.php @@ -0,0 +1,9 @@ += 7.1", - "vimeo/psalm": "^4.3" + "php": ">= 7.1" }, "autoload": { "psr-4": { diff --git a/tests/fixtures/performance/a.test b/tests/fixtures/performance/a.test index fd8f1ecea94..fcb14f8bb41 100644 --- a/tests/fixtures/performance/a.test +++ b/tests/fixtures/performance/a.test @@ -845,7 +845,7 @@ class PHPMailer case 'echo': default: //Normalize line breaks - $str = preg_replace('/\r\n|\r/ms', "\n", $str); + $str = (string) preg_replace('/\r\n|\r/ms', "\n", $str); echo gmdate('Y-m-d H:i:s'), "\t", //Trim trailing space @@ -990,7 +990,7 @@ class PHPMailer protected function addOrEnqueueAnAddress($kind, $address, $name) { $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $name = trim((string) preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim $pos = strrpos($address, '@'); if (false === $pos) { // At-sign is missing. @@ -1160,7 +1160,7 @@ class PHPMailer public function setFrom($address, $name = '', $auto = true) { $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $name = trim((string) preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim // Don't validate now addresses with IDN. Will be done in send(). $pos = strrpos($address, '@'); if (false === $pos or @@ -3090,7 +3090,7 @@ class PHPMailer $maxlen -= $maxlen % 4; $encoded = trim(chunk_split($encoded, $maxlen, "\n")); } - $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); + $encoded = (string) preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); } elseif ($matchcount > 0) { //1 or more chars need encoding, use Q-encode $encoding = 'Q'; @@ -3099,7 +3099,7 @@ class PHPMailer $encoded = $this->encodeQ($str, $position); $encoded = $this->wrapText($encoded, $maxlen, true); $encoded = str_replace('=' . static::$LE, "\n", trim($encoded)); - $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); + $encoded = (string) preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); } elseif (strlen($str) > $maxlen) { //No chars need encoding, but line is too long, so fold it $encoded = trim($this->wrapText($str, $maxlen, false)); @@ -3108,7 +3108,7 @@ class PHPMailer $encoded = trim(chunk_split($str, static::STD_LINE_LENGTH, static::$LE)); } $encoded = str_replace(static::$LE, "\n", trim($encoded)); - $encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded); + $encoded = (string) preg_replace('/^(.*)$/m', ' \\1', $encoded); } else { //No reformatting needed return $str; @@ -3784,7 +3784,7 @@ class PHPMailer static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION)) ) ) { - $message = preg_replace( + $message = (string) preg_replace( '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', $images[1][$imgindex] . '="cid:' . $cid . '"', $message @@ -4215,7 +4215,7 @@ class PHPMailer //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` //@see https://tools.ietf.org/html/rfc5322#section-2.2 //That means this may break if you do something daft like put vertical tabs in your headers. - $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); + $signHeader = (string) preg_replace('/\r\n[ \t]+/', ' ', $signHeader); $lines = explode("\r\n", $signHeader); foreach ($lines as $key => $line) { //If the header is missing a :, skip it as it's invalid @@ -4228,7 +4228,7 @@ class PHPMailer //Lower-case header name $heading = strtolower($heading); //Collapse white space within the value - $value = preg_replace('/[ \t]{2,}/', ' ', $value); + $value = (string) preg_replace('/[ \t]{2,}/', ' ', $value); //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value //But then says to delete space before and after the colon. //Net result is the same as trimming both ends of the value. diff --git a/tests/fixtures/performance/b.test b/tests/fixtures/performance/b.test index 1e607f088bf..c750465932f 100644 --- a/tests/fixtures/performance/b.test +++ b/tests/fixtures/performance/b.test @@ -845,7 +845,7 @@ class PHPMailer case 'echo': default: //Normalize line breaks - $str = preg_replace('/\r\n|\r/ms', "\n", $str); + $str = (string) preg_replace('/\r\n|\r/ms', "\n", $str); echo gmdate('Y-m-d H:i:s'), "\t", //Trim trailing space @@ -990,7 +990,7 @@ class PHPMailer protected function addOrEnqueueAnAddress($kind, $address, $name) { $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $name = trim((string) preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim $pos = strrpos($address, '@'); if (false === $pos) { // At-sign is missing. @@ -1160,7 +1160,7 @@ class PHPMailer public function setFrom($address, $name = '', $auto = true) { $address = trim($address); - $name = trim(preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim + $name = trim((string) preg_replace('/[\r\n]+/', '', $name)); //Strip breaks and trim // Don't validate now addresses with IDN. Will be done in send(). $pos = strrpos($address, '@'); if (false === $pos or @@ -3090,7 +3090,7 @@ class PHPMailer $maxlen -= $maxlen % 4; $encoded = trim(chunk_split($encoded, $maxlen, "\n")); } - $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); + $encoded = (string) preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); } elseif ($matchcount > 0) { //1 or more chars need encoding, use Q-encode $encoding = 'Q'; @@ -3099,7 +3099,7 @@ class PHPMailer $encoded = $this->encodeQ($str, $position); $encoded = $this->wrapText($encoded, $maxlen, true); $encoded = str_replace('=' . static::$LE, "\n", trim($encoded)); - $encoded = preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); + $encoded = (string) preg_replace('/^(.*)$/m', ' =?' . $this->CharSet . "?$encoding?\\1?=", $encoded); } elseif (strlen($str) > $maxlen) { //No chars need encoding, but line is too long, so fold it $encoded = trim($this->wrapText($str, $maxlen, false)); @@ -3108,7 +3108,7 @@ class PHPMailer $encoded = trim(chunk_split($str, static::STD_LINE_LENGTH, static::$LE)); } $encoded = str_replace(static::$LE, "\n", trim($encoded)); - $encoded = preg_replace('/^(.*)$/m', ' \\1', $encoded); + $encoded = (string) preg_replace('/^(.*)$/m', ' \\1', $encoded); } else { //No reformatting needed return $str; @@ -3784,7 +3784,7 @@ class PHPMailer static::_mime_types((string) static::mb_pathinfo($filename, PATHINFO_EXTENSION)) ) ) { - $message = preg_replace( + $message = (string) preg_replace( '/' . $images[1][$imgindex] . '=["\']' . preg_quote($url, '/') . '["\']/Ui', $images[1][$imgindex] . '="cid:' . $cid . '"', $message @@ -4215,7 +4215,7 @@ class PHPMailer //Note PCRE \s is too broad a definition of whitespace; RFC5322 defines it as `[ \t]` //@see https://tools.ietf.org/html/rfc5322#section-2.2 //That means this may break if you do something daft like put vertical tabs in your headers. - $signHeader = preg_replace('/\r\n[ \t]+/', ' ', $signHeader); + $signHeader = (string) preg_replace('/\r\n[ \t]+/', ' ', $signHeader); $lines = explode("\r\n", $signHeader); foreach ($lines as $key => $line) { //If the header is missing a :, skip it as it's invalid @@ -4228,7 +4228,7 @@ class PHPMailer //Lower-case header name $heading = strtolower($heading); //Collapse white space within the value - $value = preg_replace('/[ \t]{2,}/', ' ', $value); + $value = (string) preg_replace('/[ \t]{2,}/', ' ', $value); //RFC6376 is slightly unclear here - it says to delete space at the *end* of each value //But then says to delete space before and after the colon. //Net result is the same as trimming both ends of the value. diff --git a/tests/fixtures/stubs/custom_taint_source.php b/tests/fixtures/stubs/custom_taint_source.php new file mode 100644 index 00000000000..59eb33da49d --- /dev/null +++ b/tests/fixtures/stubs/custom_taint_source.php @@ -0,0 +1,3 @@ +