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 3d1103373cb..20e23d909de 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/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 40cc9965ebf..6384250bc36 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", + "php": "~8.1.17 || ~8.2.4 || ~8.3.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.2", "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.16", + "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", + "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 c15a74e6080..55aedabe061 100644 --- a/config.xsd +++ b/config.xsd @@ -48,10 +48,11 @@ + - - + + @@ -231,6 +232,7 @@ + @@ -335,7 +337,6 @@ - @@ -353,6 +354,7 @@ + @@ -436,6 +438,7 @@ + @@ -443,12 +446,14 @@ + + @@ -495,6 +500,7 @@ + diff --git a/docs/annotating_code/supported_annotations.md b/docs/annotating_code/supported_annotations.md index 346b525f878..070f3ddd682 100644 --- a/docs/annotating_code/supported_annotations.md +++ b/docs/annotating_code/supported_annotations.md @@ -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 ``` -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 @@ -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..84ac39765ee 100644 --- a/docs/running_psalm/dealing_with_code_issues.md +++ b/docs/running_psalm/dealing_with_code_issues.md @@ -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. Alternateivly 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 d0edf8c0236..2392aeb2eb7 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) @@ -262,7 +264,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) @@ -288,6 +289,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) @@ -295,11 +297,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 ae683435186..45e76af86c6 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) @@ -135,7 +136,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) @@ -153,6 +153,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) @@ -237,6 +238,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) @@ -244,12 +246,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) @@ -298,6 +302,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/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..1a6858b03cb 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -10,7 +10,7 @@ * Configuration * ************************************************************************************************************** --> - + @@ -91,8 +91,6 @@ - - - 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]]]> @@ -37,12 +32,6 @@ - - - - - - namespace]]> @@ -59,28 +48,30 @@ symbol, '::')]]> symbol, '::')]]> symbol, '\\')]]> - - - - - - - + + + + + + + + + - function_id]]> + composer_class_loader->findFile($pluginClassName)]]> autoloader]]> localName, $offset)]]> @@ -90,6 +81,9 @@ + + + file_path, 'stub')]]> file_path, 'vendor')]]> @@ -100,7 +94,6 @@ - directory]]> @@ -201,7 +194,6 @@ description]]> var_id]]> - line_number]]> @@ -288,6 +280,11 @@ defining_fqcln]]> + + + + + @@ -647,6 +644,7 @@ + @@ -658,7 +656,6 @@ calling_method_id]]> - value, '::')]]> value, '::')]]> @@ -1104,20 +1101,16 @@ - - + - + error_baseline]]> - - - threads]]> @@ -1128,7 +1121,6 @@ - @@ -1140,7 +1132,6 @@ - @@ -1226,6 +1217,9 @@ + + methods[$declaring_method_name]->stubbed]]> + @@ -1251,6 +1245,9 @@ + + methods[$implementing_method_id->method_name]->abstract]]> + @@ -1377,6 +1374,9 @@ + + + @@ -1391,7 +1391,23 @@ + + + + + + + + + + + + + + + + TCPServerAddress]]> TCPServerAddress]]> @@ -1428,7 +1444,6 @@ - parser->parse( $hacky_class_fix, $error_handler, @@ -1441,13 +1456,11 @@ - children[0]]]> children[1]]]> - @@ -1498,12 +1511,17 @@ + + + + + , string>]]> + + + - - 0]]> - @@ -1579,32 +1597,6 @@ start_change]]> - - - - - - getArgument('pluginName')]]> - getOption('config')]]> - - - - - - - - getArgument('pluginName')]]> - getOption('config')]]> - - - - - - - - getOption('config')]]> - - @@ -1769,9 +1761,6 @@ - - - properties[0]]]> properties[0]]]> @@ -2109,11 +2098,6 @@ - - - - - @@ -2145,11 +2129,6 @@ - - - - - @@ -2162,11 +2141,6 @@ - - - getGenericValueType())]]> - getGenericValueType())]]> - @@ -2189,18 +2163,6 @@ properties[0]]]> properties[0]]]> - - - - - - - - - - - type_param]]> - @@ -2214,14 +2176,6 @@ - - - - - - - - @@ -2252,11 +2206,6 @@ - - - extra_types]]> - - @@ -2331,6 +2280,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 +2320,16 @@ + + + + + + + + + + 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 a02f61403f6..bc31ac99c01 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, @@ -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); @@ -2352,7 +2051,7 @@ public function removeTemporaryFileChanges(string $file_path): void */ public function isTypeContainedByType( Union $input_type, - Union $container_type + Union $container_type, ): bool { return UnionTypeComparator::isContainedBy($this, $input_type, $container_type); } @@ -2372,7 +2071,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); } @@ -2411,20 +2110,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; @@ -2445,12 +2143,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 a4d4fe35e24..5d8cfd9e72f 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, @@ -155,7 +158,6 @@ class Config 'MixedArrayTypeCoercion', 'MixedAssignment', 'MixedFunctionCall', - 'MixedInferredReturnType', 'MixedMethodCall', 'MixedOperand', 'MixedPropertyFetch', @@ -172,7 +174,7 @@ class Config * * @var array */ - protected $universal_object_crates; + private array $universal_object_crates; /** * @var static|null @@ -181,74 +183,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 @@ -300,240 +283,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 = []; @@ -543,75 +414,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. @@ -624,7 +471,7 @@ class Config * @psalm-readonly-allow-private-mutation * @var array */ - public $php_extensions = [ + public array $php_extensions = [ "apcu" => null, "decimal" => null, "dom" => null, @@ -652,7 +499,7 @@ class Config * @var list * @readonly */ - public $php_extensions_supported_by_psalm_callmaps = [ + public array $php_extensions_supported_by_psalm_callmaps = [ 'apache', 'bcmath', 'bzip2', @@ -717,7 +564,7 @@ class Config * * @var array */ - public $php_extensions_not_supported = []; + public array $php_extensions_not_supported = []; /** * @var array @@ -833,7 +680,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; @@ -859,6 +706,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; } @@ -948,7 +796,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 @@ -974,7 +822,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); @@ -1000,7 +848,7 @@ private static function processConfigDeprecations( self $config, DOMDocument $dom_document, string $file_contents, - string $config_path + string $config_path, ): void { $config->config_issues = []; @@ -1035,7 +883,6 @@ private static function processConfigDeprecations( /** * @param non-empty-string $file_contents * @psalm-suppress MixedAssignment - * @psalm-suppress MixedArgument * @psalm-suppress MixedPropertyFetch * @throws ConfigException */ @@ -1043,7 +890,7 @@ private static function fromXmlAndPaths( string $base_dir, string $file_contents, string $current_dir, - ?string $config_path + ?string $config_path, ): self { $config = new static(); @@ -1097,6 +944,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', @@ -1112,6 +960,7 @@ private static function fromXmlAndPaths( } } + $config->source_filename = $config_path; if ($config->resolve_from_config_file) { $config->base_dir = $base_dir; } else { @@ -1123,15 +972,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 => $_) { @@ -1179,7 +1030,7 @@ private static function fromXmlAndPaths( } } - $config->autoloader = realpath($autoloader_path); + $config->autoloader = (string) realpath($autoloader_path); } if (isset($config_xml['cacheDirectory'])) { @@ -1232,18 +1083,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'])) { @@ -1403,8 +1246,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); } } @@ -1550,6 +1392,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(); @@ -1562,7 +1410,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); @@ -1588,7 +1436,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'])) { @@ -1823,8 +1671,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, @@ -1894,7 +1741,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 } } @@ -1945,7 +1792,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; @@ -2010,17 +1857,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'; } @@ -2103,6 +1950,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])) { @@ -2153,7 +2024,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); @@ -2384,19 +2255,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); @@ -2411,9 +2269,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; } @@ -2555,7 +2414,7 @@ public function visitComposerAutoloadFiles(ProjectAnalyzer $project_analyzer, ?P $codebase->classlikes->forgetMissingClassLikes(); $this->include_collector->runAndCollect( - [$this, 'requireAutoloader'], + $this->requireAutoloader(...), ); } @@ -2581,10 +2440,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; @@ -2624,7 +2481,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; } } } @@ -2759,8 +2616,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 2a04279f4e6..0a6b0cce668 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 @@ class; @@ -256,8 +258,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) { @@ -580,7 +580,7 @@ public function analyze( try { $trait_file_analyzer = $project_analyzer->getFileAnalyzerForClassLike($fq_trait_name); - } catch (Exception $e) { + } catch (Exception) { continue; } @@ -624,43 +624,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, @@ -688,7 +652,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(); @@ -943,7 +907,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 } } @@ -1017,7 +981,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; @@ -1231,7 +1195,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, ], @@ -1383,7 +1347,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(); @@ -1535,7 +1499,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; @@ -1637,7 +1601,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, @@ -1685,7 +1649,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(); @@ -1860,7 +1824,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 = []; @@ -1896,7 +1860,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; @@ -2024,7 +1988,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; @@ -2082,7 +2046,7 @@ private function checkImplementedInterfaces( try { $interface_storage = $classlike_storage_provider->get($fq_interface_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return false; } @@ -2116,7 +2080,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; } @@ -2327,7 +2291,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; @@ -2479,7 +2443,7 @@ private function checkParentClass( $code_location, $storage->template_type_extends_count[$parent_fq_class_name] ?? 0, ); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // do nothing } } @@ -2508,8 +2472,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( @@ -2522,7 +2486,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', @@ -2531,7 +2495,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 80db22ed9d1..c7ed9cedc2b 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; } @@ -363,7 +359,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; @@ -393,13 +389,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 e13308e5c0f..65d38b25096 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 ) { @@ -514,17 +515,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; } @@ -831,7 +821,7 @@ public static function checkReturnType( ProjectAnalyzer $project_analyzer, FunctionLikeAnalyzer $function_like_analyzer, FunctionLikeStorage $storage, - Context $context + Context $context, ): ?bool { $codebase = $project_analyzer->getCodebase(); @@ -1027,7 +1017,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 70786016bf7..758b44ef342 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; @@ -2132,7 +2129,7 @@ private function getFunctionInformation( private function detectUnusedParameters( StatementsAnalyzer $statements_analyzer, FunctionLikeStorage $storage, - Context $context + Context $context, ): array { $codebase = $statements_analyzer->getCodebase(); @@ -2239,6 +2236,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 3bfb9a414cc..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 bc42192496a..1c68bee7a7b 100644 --- a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -99,7 +103,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; @@ -165,7 +169,7 @@ public static function checkMethodExists( MethodIdentifier $method_id, CodeLocation $code_location, array $suppressed_issues, - ?string $calling_method_id = null + ?string $calling_method_id = null, ): ?bool { if ($codebase->methods->methodExists( $method_id, @@ -193,7 +197,7 @@ public static function checkMethodExists( public static function isMethodVisible( MethodIdentifier $method_id, Context $context, - StatementsSource $source + StatementsSource $source, ): bool { $codebase = $source->getCodebase(); @@ -278,7 +282,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 61726c5adbe..913c31d5338 100644 --- a/src/Psalm/Internal/Analyzer/MethodComparator.php +++ b/src/Psalm/Internal/Analyzer/MethodComparator.php @@ -1,5 +1,7 @@ name, @@ -237,6 +241,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 */ @@ -253,7 +307,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 @@ -360,7 +414,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(); @@ -452,7 +506,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, '__') && $config->isInProjectDirs( $implementer_param->location->file_path, ) @@ -500,7 +554,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, @@ -572,7 +629,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( @@ -736,7 +793,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, @@ -789,7 +846,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); @@ -803,7 +860,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); @@ -870,6 +927,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( @@ -902,7 +971,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, @@ -995,7 +1064,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, @@ -1097,6 +1166,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( @@ -1120,7 +1200,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 ed22c279d97..8b02ae9a255 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) { @@ -347,7 +338,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; @@ -511,6 +502,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"); @@ -598,7 +590,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); } @@ -924,7 +916,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, @@ -954,7 +946,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'); @@ -1036,6 +1028,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(); @@ -1055,9 +1050,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); @@ -1151,7 +1143,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; @@ -1195,8 +1187,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(); } @@ -1265,7 +1256,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; @@ -1312,7 +1303,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 8f0ec8a713f..d71628cdfb9 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]; } @@ -733,13 +728,13 @@ public static function handleIterable( Context $context, ?Union &$key_type, ?Union &$value_type, - bool &$has_valid_iterator + bool &$has_valid_iterator, ): 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]; } @@ -956,7 +951,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 @@ -1033,7 +1028,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; @@ -1092,7 +1087,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 ebbc0a66e4a..8e0e23ec18d 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 00c7895c2be..f4aa30a1982 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 b4648cd59f2..b14891ce0f9 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 f925f961336..60e3acaa964 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) { @@ -241,8 +242,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) { @@ -518,17 +519,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 ) { @@ -3795,7 +3775,7 @@ private static function getArrayKeyExistsAssertions( if ($first_arg->value instanceof PhpParser\Node\Scalar\String_) { $first_var_name = '\'' . $first_arg->value->value . '\''; - } elseif ($first_arg->value instanceof PhpParser\Node\Scalar\LNumber) { + } elseif ($first_arg->value instanceof PhpParser\Node\Scalar\Int_) { $first_var_name = (string)$first_arg->value->value; } } @@ -3855,7 +3835,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 = []; @@ -3968,7 +3948,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; @@ -4078,7 +4058,7 @@ private static function getAndCheckInstanceofAssertions( ?Codebase $codebase, FileSource $source, ?string $this_class_name, - bool $inside_negation + bool $inside_negation, ): array { $if_types = []; @@ -4160,7 +4140,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(); @@ -4213,7 +4193,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 01150233a66..91d28b02088 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 @@ -1093,12 +1084,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 bedec945c25..e96263fd83a 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() @@ -1228,9 +1231,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 ) { @@ -1373,7 +1373,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)) { @@ -1413,7 +1413,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 = @@ -1594,7 +1594,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; @@ -1699,7 +1699,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 d721f481587..5356280bd5e 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 f4ec365b722..04e1c9e2e48 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 6a59185b55f..f06c4f8a1b6 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 2a2e0cf8217..e7134d07487 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 309d1b8d537..4557e1cfebf 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()); @@ -390,7 +392,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'); @@ -472,7 +474,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(); @@ -486,7 +488,7 @@ private static function checkForImpureEqualityComparison( '__tostring', ), ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { continue; } @@ -520,7 +522,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 93d17c3f7f5..1f36642ecab 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BooleanNotAnalyzer.php @@ -1,5 +1,7 @@ getCodebase(); @@ -240,7 +243,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) { @@ -331,10 +334,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 ) { @@ -676,7 +675,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(); @@ -686,7 +685,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); @@ -855,9 +854,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( @@ -940,10 +936,6 @@ public static function verifyType( $potential_method_ids = []; 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) { $potential_method_id = CallableTypeComparator::getCallableMethodIdFromTKeyedArray( $input_type_part, @@ -1208,7 +1200,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(); @@ -1278,7 +1270,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); } @@ -1384,12 +1376,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) { @@ -1428,74 +1430,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; + $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(); - } - - 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; } } @@ -1510,7 +1505,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 81843d56a15..25bb031396e 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_call = $was_inside_call; @@ -316,7 +316,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(); @@ -371,7 +371,7 @@ private static function handleClosureArg( TemplateResult $template_result, int $argument_offset, PhpParser\Node\Arg $arg, - FunctionLikeParameter $param + FunctionLikeParameter $param, ): void { if (!$param->type) { return; @@ -451,7 +451,7 @@ private static function handleClosureArg( $statements_analyzer->getFilePath(), $closure_id, ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { return; } @@ -532,7 +532,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 @@ -540,13 +539,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; @@ -989,7 +988,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 @@ -1040,9 +1039,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; @@ -1139,7 +1138,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, ); } @@ -1153,7 +1152,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 @@ -1172,7 +1171,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 @@ -1261,7 +1260,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; @@ -1300,7 +1299,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, @@ -1310,7 +1309,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 @@ -1477,7 +1476,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, @@ -1557,7 +1556,6 @@ private static function getProvisionalTemplateResultForFunctionLike( /** * @param array $args - * @param string|MethodIdentifier|null $method_id * @param array $function_params */ private static function checkArgCount( @@ -1570,9 +1568,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) @@ -1627,14 +1625,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; @@ -233,7 +233,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(); } @@ -279,7 +279,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 { @@ -315,7 +315,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); @@ -360,12 +360,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); } @@ -541,7 +536,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; @@ -705,7 +700,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 526e6ee1141..54921838c0c 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 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 bf8d1af4239..248f5a48fc9 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 0a81ee8ecac..abfe8c080d1 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 ceff9788724..3b9b9cbc7dd 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 1dbf404200e..1b8519d849c 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); @@ -638,7 +640,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; @@ -745,7 +747,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 a357afac605..5cd62f721c5 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 ad9fd724f4e..85f4d465fa8 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(); @@ -668,7 +670,7 @@ private static function handleNamedCall( } $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, @@ -907,7 +909,7 @@ private static function checkPseudoMethod( array $args, ClassLikeStorage $class_storage, MethodStorage $pseudo_method_storage, - Context $context + Context $context, ): ?bool { if (ArgumentsAnalyzer::analyze( $statements_analyzer, @@ -962,7 +964,7 @@ private static function checkPseudoMethod( new CodeLocation($statements_analyzer, $stmt), $context, ); - } catch (Exception $e) { + } catch (Exception) { // do nothing } } @@ -1010,7 +1012,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; @@ -1085,7 +1087,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]; @@ -1122,7 +1124,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 bbcb0e3105a..e2f2544f0ca 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) { @@ -195,9 +195,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; @@ -242,9 +239,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; @@ -299,7 +293,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(), @@ -312,7 +306,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(); @@ -328,10 +322,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; @@ -394,7 +384,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) { @@ -498,7 +488,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(); @@ -514,10 +504,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; @@ -579,7 +565,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) { @@ -684,7 +670,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(); @@ -872,7 +858,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; @@ -884,7 +870,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 92025d0bb2f..8a9fd2cb91c 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); @@ -746,7 +748,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( @@ -844,7 +846,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 8dbcca2f9bb..1fbd8f8c86f 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_ ) { $offset = $stmt->dim instanceof PhpParser\Node\Scalar\String_ ? '\'' . $stmt->dim->value . '\'' diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Fetch/ArrayFetchAnalyzer.php index 753e2891920..b6a3ec6d85e 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, @@ -369,7 +370,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)) @@ -470,7 +471,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(); @@ -500,7 +501,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(); @@ -576,10 +577,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; @@ -900,7 +897,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; @@ -948,7 +945,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; @@ -1032,7 +1029,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 @@ -1120,7 +1117,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; @@ -1251,7 +1248,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) { @@ -1415,7 +1412,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()); @@ -1525,7 +1522,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(); @@ -1750,7 +1747,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' @@ -1903,7 +1900,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()) { @@ -1986,7 +1983,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 3d60782d1b9..93b6d68ea48 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); } @@ -510,11 +511,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; } } } @@ -546,7 +543,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(); @@ -626,11 +623,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( @@ -733,7 +730,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 @@ -782,12 +779,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 7524c523baa..9c574e7a0f8 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 063277aaff6..9261457eff1 100644 --- a/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/ExpressionAnalyzer.php @@ -1,5 +1,7 @@ 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; @@ -255,7 +253,7 @@ private static function handleExpression( $stmt, $context, 0, - $from_stmt, + $from_stmt !== null, ); } @@ -275,7 +273,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); } @@ -342,7 +340,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", @@ -375,7 +373,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()); @@ -419,7 +417,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); } @@ -437,7 +435,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), ), @@ -459,7 +457,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, @@ -467,12 +465,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; } @@ -486,7 +484,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(); @@ -514,7 +512,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 4f89f5de1d8..c8df750b6b3 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,6 +798,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()); @@ -878,7 +872,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; } @@ -982,7 +976,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; @@ -1109,7 +1103,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..10de0e5eff5 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(); @@ -29,7 +29,6 @@ public function __construct( $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 0fa174eff1f..40267ebef4e 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'])) { @@ -416,7 +417,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; } @@ -448,8 +449,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) @@ -462,8 +463,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) @@ -500,9 +501,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; @@ -555,7 +556,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, @@ -581,7 +582,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); @@ -596,9 +597,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 @@ -639,40 +640,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); + $errorBaseline = ((string) $options['set-baseline']) + ?: $config->error_baseline ?: Config::DEFAULT_BASELINE_NAME; + try { $issue_baseline = ErrorBaseline::read( new FileProvider, - $options['set-baseline'], + $errorBaseline, ); - } catch (ConfigException $e) { + } catch (ConfigException) { $issue_baseline = []; } ErrorBaseline::create( new FileProvider, - $options['set-baseline'], + $errorBaseline, 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 $errorBaseline."); - CliUtils::updateConfigFile( - $config, - $path_to_config ?? $current_dir, - $options['set-baseline'], - ); + if ($errorBaseline !== $config->error_baseline) { + CliUtils::updateConfigFile( + $config, + $path_to_config ?? $current_dir, + $errorBaseline, + ); + } fwrite(STDERR, PHP_EOL); @@ -757,7 +763,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(); @@ -799,7 +805,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); @@ -816,8 +822,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(); @@ -829,8 +834,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(); @@ -905,12 +909,16 @@ private static function restart(array $options, int $threads, Progress $progress 'blackfire', ]); + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + $ini_handler->disableExtensions(['opcache', 'Zend OPcache']); + } + // If Xdebug is enabled, restart without it $ini_handler->check(); - if (!function_exists('opcache_get_status')) { + if (!function_exists('opcache_get_status') && !defined('PHP_WINDOWS_VERSION_MAJOR')) { $progress->write(PHP_EOL - . 'Install the opcache extension to make use of JIT on PHP 8.0+ for a 20%+ performance boost!' + . 'Install the opcache extension to make use of JIT for a 20%+ performance boost!' . PHP_EOL . PHP_EOL); } } @@ -994,7 +1002,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'])) { @@ -1029,11 +1037,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); @@ -1105,7 +1113,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'])) { @@ -1133,17 +1141,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; @@ -1181,16 +1188,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; @@ -1205,31 +1202,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']; @@ -1307,11 +1289,13 @@ private static function getHelpText(): string Output the taint graph using the DOT language – requires --taint-analysis Issue baselines: - --set-baseline=PATH + --set-baseline[=PATH] Save all current error level issues to a file, to mark them as info in subsequent runs Add --include-php-versions to also include a list of PHP extension versions + Default value is `psalm-baseline.xml` + --use-baseline=PATH Allows you to use a baseline other than the default baseline provided in your config diff --git a/src/Psalm/Internal/Cli/Psalter.php b/src/Psalm/Internal/Cli/Psalter.php index d42a1f10843..f03fb8a3aa6 100644 --- a/src/Psalm/Internal/Cli/Psalter.php +++ b/src/Psalm/Internal/Cli/Psalter.php @@ -1,5 +1,7 @@ $_) { // 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 b23bcb863c2..2dd4507e7d2 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,7 +286,7 @@ 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( @@ -346,7 +351,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 +412,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 72825b05baf..80cdba04ddc 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']); } @@ -600,7 +596,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; } @@ -622,7 +618,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]; @@ -633,7 +629,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; @@ -676,7 +672,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( @@ -1188,7 +1184,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'); @@ -1205,7 +1201,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'); @@ -1637,6 +1633,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 552fab265d5..b96526be8bb 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; } @@ -1793,7 +1779,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; } @@ -1951,7 +1937,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; } @@ -2020,7 +2006,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; } @@ -2386,7 +2372,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; } } @@ -2397,7 +2383,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( @@ -2459,7 +2445,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( @@ -2481,7 +2467,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 fc6940b1cfb..35d0c77058d 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 @@ 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(); @@ -99,7 +92,7 @@ public function methodExists( ?StatementsSource $source = null, ?string $source_file_path = null, bool $use_method_existence_provider = true, - bool $is_used = false + bool $is_used = false, ): bool { $fq_class_name = $method_id->fq_class_name; $method_name = $method_id->method_name; @@ -123,7 +116,7 @@ public function methodExists( try { $class_storage = $this->classlike_storage_provider->get($fq_class_name); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { return false; } @@ -346,7 +339,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; @@ -457,6 +450,13 @@ public function getMethodParams( foreach ($params as $i => $param) { if (isset($overridden_storage->params[$i]->type) && $overridden_storage->params[$i]->has_docblock_type + && ( + ! $param->type + || $param->type->equals( + $overridden_storage->params[$i]->signature_type + ?? $overridden_storage->params[$i]->type, + ) + ) ) { $params[$i] = clone $param; /** @var Union $params[$i]->type */ @@ -492,7 +492,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; @@ -515,7 +515,7 @@ public static function localizeType( */ public static function getExtendedTemplatedTypes( TTemplateParam $atomic_type, - array $extends + array $extends, ): array { $extra_added_types = []; @@ -558,7 +558,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; @@ -588,6 +588,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; } @@ -632,9 +633,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); @@ -811,7 +811,7 @@ public function getMethodReturnType( $overridden_storage_return_type, $source_analyzer->getCodebase(), ); - } catch (InvalidArgumentException $e) { + } catch (InvalidArgumentException) { // TODO: fix } } else { @@ -938,7 +938,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); @@ -978,7 +978,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); @@ -996,7 +996,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); @@ -1008,7 +1008,7 @@ public function setAppearingMethodId( /** @psalm-mutation-free */ public function getDeclaringMethodId( - MethodIdentifier $method_id + MethodIdentifier $method_id, ): ?MethodIdentifier { $fq_class_name = $this->classlikes->getUnAliasedName($method_id->fq_class_name); @@ -1021,6 +1021,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]); } @@ -1031,7 +1033,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); @@ -1140,14 +1142,18 @@ public function getStorage(MethodIdentifier $method_id): MethodStorage } $method_name = $method_id->method_name; + $method_storage = $class_storage->methods[$method_name] + ?? $class_storage->pseudo_methods[$method_name] + ?? $class_storage->pseudo_static_methods[$method_name] + ?? null; - if (!isset($class_storage->methods[$method_name])) { + if (! $method_storage) { throw new UnexpectedValueException( '$storage should not be null for ' . $method_id, ); } - return $class_storage->methods[$method_name]; + return $method_storage; } /** @psalm-mutation-free */ @@ -1155,7 +1161,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..4093c263db5 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 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 c30d6c5a022..eabb6673e90 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. @@ -425,6 +409,7 @@ function () { $i = 0; foreach ($files_to_scan as $file_path => $_) { + $this->progress->taskDone(0); $this->scanAPath($i, $file_path); ++$i; } @@ -524,7 +509,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); @@ -618,7 +603,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)); @@ -664,7 +649,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; @@ -680,7 +665,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..ddd366476ff 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 = []; @@ -449,6 +451,33 @@ private function getChildNodes( ); break; + case TaintKind::INPUT_XPATH: + $issue = new TaintedXpath( + 'Detected tainted xpath query', + $issue_location, + $issue_trace, + $path, + ); + break; + + case TaintKind::INPUT_SLEEP: + $issue = new TaintedSleep( + 'Detected tainted sleep', + $issue_location, + $issue_trace, + $path, + ); + break; + + case TaintKind::INPUT_EXTRACT: + $issue = new TaintedExtract( + 'Detected tainted extract', + $issue_location, + $issue_trace, + $path, + ); + break; + default: $issue = new TaintedCustom( 'Detected tainted ' . $matching_taint, @@ -521,7 +550,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 e4b1b44966f..9a41edf165d 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(); @@ -69,7 +71,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( @@ -91,7 +93,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 08a972ede7d..64fc2ade52f 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 @@ -102,16 +98,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, @@ -125,7 +119,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"; @@ -192,14 +186,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); @@ -221,7 +215,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(), @@ -229,7 +223,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)); } @@ -240,7 +234,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 @@ -347,15 +341,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 af4d83776b8..cfc7bff1238 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' => 1_000_000, + 'jit_max_side_traces' => 1_000_000, + 'jit_max_exit_counters' => 1_000_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, @@ -66,19 +82,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 && !defined('PHP_WINDOWS_VERSION_MAJOR')) { // 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; } } @@ -131,10 +146,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); } @@ -145,16 +161,11 @@ 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=512M', - '-dopcache.jit=1205', - '-dopcache.optimization_level=0x7FFEBFFF', - '-dopcache.preload=', - '-dopcache.log_verbosity_level=0', - ]; + if ($opcache_loaded && !defined('PHP_WINDOWS_VERSION_MAJOR')) { + $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 4575aa3d575..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'); } }); } @@ -145,8 +142,6 @@ public function makeProgress(string $token): ProgressInterface /** * Configuration Refreshed from Client - * - * @param array $config */ private function configurationRefreshed(array $config): void { @@ -157,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 999f95df554..de5cd1a2fa5 100644 --- a/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php +++ b/src/Psalm/Internal/PhpVisitor/Reflector/ClassLikeNodeScanner.php @@ -1,5 +1,7 @@ */ @@ -113,10 +106,6 @@ final class ClassLikeNodeScanner */ public array $class_template_types = []; - private ?Name $namespace_name = null; - - private Aliases $aliases; - public ?ClassLikeStorage $storage = null; /** @@ -125,19 +114,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 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; } /** @@ -222,7 +206,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; @@ -511,7 +495,7 @@ public function start(PhpParser\Node\Stmt\ClassLike $node): ?bool ); $storage->yield = $yield_type; - } catch (TypeParseTreeException $e) { + } catch (TypeParseTreeException) { // do nothing } } @@ -745,16 +729,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; } } } @@ -881,7 +864,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), @@ -953,7 +936,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), ); } } @@ -974,7 +957,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( @@ -1058,7 +1041,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( @@ -1144,7 +1127,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( @@ -1260,7 +1243,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( @@ -1371,7 +1354,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; @@ -1424,7 +1407,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( @@ -1452,9 +1435,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( @@ -1529,7 +1512,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) { @@ -1554,7 +1537,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; @@ -1634,6 +1617,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; @@ -1878,7 +1871,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); @@ -1909,7 +1902,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]), @@ -188,10 +192,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", @@ -215,10 +219,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", @@ -253,7 +257,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') { @@ -358,7 +362,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], @@ -383,7 +387,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'])); if (preg_match('/^[4578]\.\d(\.\d+)?$/', $since)) { $since_parts = explode('.', $since); @@ -414,6 +418,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) { @@ -443,7 +448,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()); } @@ -567,7 +572,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'); } @@ -577,7 +582,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); } } @@ -596,7 +601,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); @@ -700,7 +705,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 270529ee306..7a42b0f93b5 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, @@ -510,9 +512,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] @@ -525,7 +526,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] @@ -538,7 +539,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] @@ -568,7 +569,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; @@ -703,7 +704,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 . '::' : ''; @@ -719,7 +720,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); } @@ -913,11 +914,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; } @@ -941,7 +937,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 @@ -1077,7 +1073,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) { @@ -1090,7 +1086,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); @@ -1098,7 +1094,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()); @@ -1116,7 +1112,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); @@ -1165,7 +1161,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( @@ -1220,7 +1216,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 = []; @@ -1253,7 +1249,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, @@ -1263,7 +1259,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, ); } @@ -1300,7 +1296,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, @@ -1310,7 +1306,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, ); } @@ -1347,7 +1343,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, @@ -1357,7 +1353,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, ); } @@ -1381,7 +1377,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); @@ -1433,7 +1429,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 = []; @@ -1502,7 +1498,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 f0a8f34cc63..bebfb1026ee 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 ?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 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 d49ebcee48b..ec9822a2114 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 734cd64dab8..134243213b7 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 aa4202d032c..2f26b8e32b7 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..fefb161bce5 100644 --- a/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php +++ b/src/Psalm/Internal/Provider/FileReferenceCacheProvider.php @@ -1,5 +1,7 @@ >|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 +248,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 +285,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 b89a2604128..77f161b61e5 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 ada2e2825c6..edf35f8b169 100644 --- a/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.php +++ b/src/Psalm/Internal/Provider/ReturnTypeProvider/ArrayFilterReturnTypeProvider.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 @@ 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; @@ -341,7 +341,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 +399,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 +502,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 +607,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 +635,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 +648,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 +667,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 +1471,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 c6ea6a15fdf..7d56d35cce6 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 0b3478ea978..e024bc95fc0 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)) @@ -115,10 +111,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) { @@ -314,14 +310,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 ) { @@ -384,8 +380,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; } @@ -804,14 +800,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) @@ -826,9 +818,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 4c7c3b8c7df..047ddd08bda 100644 --- a/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/CallableTypeComparator.php @@ -1,5 +1,7 @@ is_pure && !$input_type_part->is_pure) { if ($atomic_comparison_result) { @@ -172,11 +172,9 @@ public static function isNotExplicitlyCallableTypeCallable( Codebase $codebase, Atomic $input_type_part, TCallable $container_type_part, - ?TypeComparisonResult $atomic_comparison_result + ?TypeComparisonResult $atomic_comparison_result, ): bool { - if ($input_type_part instanceof TList) { - $input_type_part = $input_type_part->getKeyedArray(); - } + if ($input_type_part instanceof TArray) { if ($input_type_part->type_params[1]->isMixed() || $input_type_part->type_params[1]->hasScalar() @@ -192,15 +190,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); @@ -222,7 +211,7 @@ public static function isNotExplicitlyCallableTypeCallable( if (!$codebase->methods->hasStorage($method_id)) { return false; } - } catch (Exception $e) { + } catch (Exception) { return false; } } @@ -252,11 +241,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; } @@ -319,7 +306,7 @@ public static function getCallableFromAtomic( $return_type, $function_storage->pure, ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { if (InternalCallMapHandler::inCallMap($input_type_part->value)) { $args = []; @@ -384,7 +371,7 @@ public static function getCallableFromAtomic( $converted_return_type, $method_storage->pure, ); - } catch (UnexpectedValueException $e) { + } catch (UnexpectedValueException) { // do nothing } } @@ -474,8 +461,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 c688407cad6..f6ba06538c7 100644 --- a/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php +++ b/src/Psalm/Internal/Type/Comparator/ScalarTypeComparator.php @@ -1,5 +1,7 @@ type_coerced = true; @@ -117,10 +118,10 @@ public static function isContainedBy( } if ($input_type_part instanceof TCallableString - && (get_class($container_type_part) === TSingleLetter::class - || get_class($container_type_part) === TNonEmptyString::class - || get_class($container_type_part) === TNonFalsyString::class - || get_class($container_type_part) === TLowercaseString::class) + && ($container_type_part::class === TSingleLetter::class + || $container_type_part::class === TNonEmptyString::class + || $container_type_part::class === TNonFalsyString::class + || $container_type_part::class === TLowercaseString::class) ) { return true; } @@ -279,12 +280,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; @@ -321,15 +322,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; @@ -366,7 +367,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; @@ -375,8 +376,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 @@ -421,7 +422,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) ) { @@ -434,15 +435,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; } @@ -501,7 +502,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 53bc98bbbc8..2c3c8557222 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 1b652f09758..e511955af42 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; @@ -141,6 +140,7 @@ public function create(): ParseTree case 'is': case 'as': + case 'of': $this->handleIsOrAs($type_token); break; @@ -769,7 +769,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 @@ -822,13 +822,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 + && strpos($nexter_token[0], '@') !== false + && $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 c8bd3c1b21d..de17a9cb31f 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; @@ -1126,52 +1121,52 @@ 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 ) { // do nothing @@ -1189,7 +1184,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; @@ -1247,8 +1242,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(); } @@ -1330,7 +1325,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 []; } @@ -1359,7 +1354,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 = []; @@ -1432,7 +1427,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 { @@ -1477,7 +1471,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; @@ -1538,14 +1532,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 0855a1ab732..4c96ef53289 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 fd807f584b1..4e932499022 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] === '\'') { @@ -457,7 +460,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( @@ -570,7 +573,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 */ @@ -579,8 +581,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 = []; @@ -966,7 +968,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', @@ -1006,7 +1008,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', @@ -1025,7 +1027,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 @@ -1084,7 +1086,7 @@ private static function getTypeFromUnionTree( Codebase $codebase, array $template_type_map, array $type_aliases, - bool $from_docblock + bool $from_docblock, ): Union { $has_null = false; @@ -1150,7 +1152,7 @@ private static function getTypeFromIntersectionTree( Codebase $codebase, array $template_type_map, array $type_aliases, - bool $from_docblock + bool $from_docblock, ): Atomic { $intersection_types = []; @@ -1227,6 +1229,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) { @@ -1262,7 +1265,6 @@ private static function getTypeFromIntersectionTree( /** * @param array> $template_type_map * @param array $type_aliases - * @return TCallable|TClosure * @throws TypeParseTreeException */ private static function getTypeFromCallableTree( @@ -1270,8 +1272,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) { @@ -1296,7 +1298,7 @@ private static function getTypeFromCallableTree( $is_optional = $child_tree->has_default; } 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( @@ -1324,7 +1326,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); @@ -1340,7 +1342,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'); @@ -1375,7 +1377,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'); } @@ -1391,7 +1393,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( @@ -1399,8 +1400,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 = []; @@ -1527,7 +1528,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; @@ -1602,7 +1603,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; @@ -1640,7 +1641,7 @@ private static function extractKeyedIntersectionTypes( continue; } - if (get_class($intersection_type) === TObject::class) { + if ($intersection_type::class === TObject::class) { continue; } @@ -1656,7 +1657,7 @@ private static function extractKeyedIntersectionTypes( throw new TypeParseTreeException( 'Intersection types must be all objects, ' - . get_class($intersection_type) . ' provided', + . $intersection_type::class . ' provided', ); } @@ -1732,7 +1733,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 33fc16bc800..2e6690b9252 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 +163,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 +190,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(); @@ -273,7 +273,7 @@ public static function add(CodeIssue $e, bool $is_fixable = false): bool 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 +542,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 +650,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 +713,7 @@ public static function finish( try { $source_control_info = (new GitInfoCollector())->collect(); - } catch (RuntimeException $e) { + } catch (RuntimeException) { // do nothing } @@ -791,6 +831,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 +903,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 +912,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 fbbcb3a36fd..c89821f0612 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 f699a6d4e03..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 249ab125b38..3a2cd512fc7 100644 --- a/src/Psalm/Plugin/EventHandler/Event/BeforeFileAnalysisEvent.php +++ b/src/Psalm/Plugin/EventHandler/Event/BeforeFileAnalysisEvent.php @@ -1,5 +1,7 @@ statements_source = $statements_source; - $this->file_context = $file_context; - $this->file_storage = $file_storage; - $this->codebase = $codebase; } 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 39125c366bd..12736963ee3 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 */ 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 ee6128e787f..da1fe09ecf0 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; @@ -931,7 +933,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 } } @@ -993,7 +995,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 46092ec8162..15bd44a8667 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); } @@ -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 7ef847d4ab1..cbf8141d1db 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 d4992523b06..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 764f1ca2c4d..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 3287617ec87..58452bd5ce9 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 f9c676f47af..7a157470e43 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 c1c29b991c8..189784ec3f0 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 744959d01f3..1e1bd54c8b9 100644 --- a/src/Psalm/Type/Reconciler.php +++ b/src/Psalm/Type/Reconciler.php @@ -1,5 +1,7 @@ $existing_referenced) { if ($existing_referenced === $new_key) { @@ -467,12 +470,12 @@ 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); $new_base_key = $base_key . '[' . $array_key . ']'; - if (strpos($array_key, '\'') !== false) { + if (str_contains($array_key, '\'')) { $new_types[$base_key][] = [new HasStringArrayAccess()]; } else { $new_types[$base_key][] = [new HasIntOrStringArrayAccess()]; @@ -660,7 +663,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); @@ -704,7 +707,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 . ']'; @@ -717,9 +720,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()); @@ -816,7 +817,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])) { @@ -844,7 +845,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)), @@ -933,7 +934,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; @@ -942,11 +943,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( @@ -983,18 +980,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] === '!'; @@ -1116,7 +1112,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); @@ -1153,11 +1149,8 @@ private static function adjustTKeyedArrayType( ); 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 a500789cf41..9506aaa5049 100644 --- a/src/Psalm/Type/UnionTrait.php +++ b/src/Psalm/Type/UnionTrait.php @@ -1,5 +1,7 @@ 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"); @@ -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 62e0ff33750..3b43831e6ff 100644 --- a/stubs/CoreGenericClasses.phpstub +++ b/stubs/CoreGenericClasses.phpstub @@ -492,6 +492,16 @@ final class WeakMap implements ArrayAccess, Countable, IteratorAggregate, Traver * @return void */ public function offsetUnset($offset) {} + + /** + * Create a new iterator from an ArrayObject instance + * @link http://php.net/manual/en/arrayobject.getiterator.php + * + * @return \Traversable An iterator from an ArrayObject. + * + * @since 5.0.0 + */ + public function getIterator() { } } class mysqli diff --git a/stubs/CoreGenericFunctions.phpstub b/stubs/CoreGenericFunctions.phpstub index e793b13d22b..a5f996beca3 100644 --- a/stubs/CoreGenericFunctions.phpstub +++ b/stubs/CoreGenericFunctions.phpstub @@ -1799,7 +1799,34 @@ 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 {} + +/** + * @psalm-taint-sink extract $array + */ +function extract(array &$array, int $flags = EXTR_OVERWRITE, string $prefix = ""): int {} 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)) { @@ -2115,6 +2117,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 fc8c36d212f..e965ad43008 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', ], ], @@ -1438,6 +1438,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 e8fe9a94c40..6757b43088f 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'))); } public function testIgnoreSymlinkedProjectDirectory(): void @@ -199,9 +201,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(); @@ -246,10 +248,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 @@ -271,9 +273,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 @@ -295,9 +297,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 @@ -319,10 +321,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 @@ -346,11 +348,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 @@ -374,10 +376,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 @@ -401,8 +403,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 @@ -422,7 +424,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( @@ -439,7 +441,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( @@ -456,7 +458,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( @@ -473,7 +475,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 @@ -530,8 +532,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 @@ -610,7 +612,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'info', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -618,7 +620,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'error', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); @@ -626,7 +628,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'error', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('src/psalm.php'), + (string) realpath('src/psalm.php'), ), ); @@ -634,7 +636,7 @@ public function testIssueHandlerWithCustomErrorLevels(): void 'info', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('examples/TemplateChecker.php'), + (string) realpath('examples/TemplateChecker.php'), ), ); @@ -843,7 +845,7 @@ public function testIssueHandlerSetDynamically(): void 'info', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -851,7 +853,7 @@ public function testIssueHandlerSetDynamically(): void 'error', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), + (string) realpath('src/Psalm/Internal/Analyzer/FileAnalyzer.php'), ), ); @@ -859,7 +861,7 @@ public function testIssueHandlerSetDynamically(): void 'error', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('src/psalm.php'), + (string) realpath('src/psalm.php'), ), ); @@ -867,7 +869,7 @@ public function testIssueHandlerSetDynamically(): void 'info', $config->getReportingLevelForFile( 'PossiblyInvalidArgument', - realpath('examples/TemplateChecker.php'), + (string) realpath('examples/TemplateChecker.php'), ), ); @@ -1019,7 +1021,7 @@ public function testIssueHandlerOverride(): void 'info', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -1027,14 +1029,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'), ), ); } @@ -1078,7 +1080,7 @@ public function testIssueHandlerSafeOverride(): void 'error', $config->getReportingLevelForFile( 'MissingReturnType', - realpath('src/Psalm/Type.php'), + (string) realpath('src/Psalm/Type.php'), ), ); @@ -1086,14 +1088,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'), ), ); } @@ -1167,7 +1169,7 @@ public function testThing(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1202,7 +1204,7 @@ public function testValidThrowInvalidCatch(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1250,7 +1252,7 @@ public function testInvalidThrowValidCatch(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1301,7 +1303,7 @@ public function testValidThrowValidCatch(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1374,7 +1376,7 @@ public function testGlobals(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1472,7 +1474,7 @@ public function testIgnoreExceptions(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1538,7 +1540,7 @@ public function testNotIgnoredException(): void ), ); - $file_path = getcwd() . '/src/somefile.php'; + $file_path = (string) getcwd() . '/src/somefile.php'; $this->addFile( $file_path, @@ -1755,14 +1757,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 @@ -1788,14 +1790,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 @@ -1849,7 +1851,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 @@ -1878,14 +1880,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'))); } /** @@ -1909,7 +1911,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, @@ -1940,7 +1942,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 +133,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( $file_path, @@ -169,7 +171,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, @@ -207,7 +209,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, @@ -245,7 +247,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, @@ -282,7 +284,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, @@ -320,7 +322,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, @@ -363,7 +365,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, @@ -401,7 +403,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, @@ -434,7 +436,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, @@ -536,7 +538,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, @@ -575,7 +577,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, @@ -636,7 +638,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, @@ -672,7 +674,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, @@ -713,7 +715,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, @@ -756,7 +758,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, @@ -889,7 +891,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, @@ -929,7 +931,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, @@ -1001,7 +1003,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 142be4a3e8a..c68a7f80b05 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 + { + 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 74c0cb6eae6..a71f59787fb 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(); } @@ -458,13 +458,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); @@ -206,7 +226,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, @@ -215,7 +235,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', ); } @@ -223,8 +243,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); @@ -240,7 +261,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); @@ -257,12 +279,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" ' @@ -281,6 +304,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 03e7f714ec2..33d82e7c7a7 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 c2ca6040367..c44b0f45e67 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]; } } @@ -626,7 +628,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/Provider/ClassLikeStorageInstanceCacheProvider.php b/tests/Internal/Provider/ClassLikeStorageInstanceCacheProvider.php index 1b81870c9eb..657ac4a2d93 100644 --- a/tests/Internal/Provider/ClassLikeStorageInstanceCacheProvider.php +++ b/tests/Internal/Provider/ClassLikeStorageInstanceCacheProvider.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 d7b56e5fa48..cd6d00358d5 100644 --- a/tests/Loop/ForeachTest.php +++ b/tests/Loop/ForeachTest.php @@ -1,5 +1,7 @@ [ 'code' => ' [ + 'code' => ' [], + 'ignored_issues' => ['ParamNameMismatch'], + ], ]; } @@ -1116,6 +1133,21 @@ class B extends A {} $b->foo();', 'error_message' => 'UndefinedMagicMethod', ], + 'inheritSealedMethodsWithoutPrefix' => [ + 'code' => 'foo();', + 'error_message' => 'UndefinedMagicMethod', + ], 'lonelyMethod' => [ 'code' => ' 'UndefinedVariable', ], + 'MagicMethodReturnTypesCheckedForClasses' => [ + 'code' => ' 'ImplementedReturnTypeMismatch', + ], + 'MagicMethodParamTypesCheckedForClasses' => [ + 'code' => ' 'ImplementedParamTypeMismatch', + ], + 'MagicMethodReturnTypesCheckedForInterfaces' => [ + 'code' => ' 'ImplementedReturnTypeMismatch', + ], + 'MagicMethodParamTypesCheckedForInterfaces' => [ + 'code' => ' 'ImplementedParamTypeMismatch', + ], + 'SKIPPED-MagicMethodMadeConcreteChecksParams' => [ + 'code' => ' 'ImplementedParamTypeMismatch', + ], ]; } diff --git a/tests/MagicPropertyTest.php b/tests/MagicPropertyTest.php index 27b5b36a5ea..abb03aed1a0 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' => [ @@ -1361,7 +1363,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 1769546aab2..807fc4a57a5 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 +228,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 +283,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 +321,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 +362,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', + ], ]; } diff --git a/tests/Template/ClassStringMapTest.php b/tests/Template/ClassStringMapTest.php index c5105462d18..78ed8b7f354 100644 --- a/tests/Template/ClassStringMapTest.php +++ b/tests/Template/ClassStringMapTest.php @@ -1,5 +1,7 @@ '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 f37d0033584..e5a06f4e83c 100644 --- a/tests/Traits/InvalidCodeAnalysisTestTrait.php +++ b/tests/Traits/InvalidCodeAnalysisTestTrait.php @@ -1,5 +1,7 @@ getTestName(); if (strpos($test_name, 'PHP80-') !== false) { @@ -80,7 +82,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/ValidCodeAnalysisTestTrait.php b/tests/Traits/ValidCodeAnalysisTestTrait.php index 7a76481d275..cd39b1ab895 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 6cc6133f5e2..9d5658d3236 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 74f06e44ea1..59c66fdfbad 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 @@ +